Commit baf2cd72 authored by Bryce Johnson's avatar Bryce Johnson

Merge branch 'master' into repository-page-ui-issues

parents e6aa1c52 faac7121
...@@ -5,8 +5,8 @@ require: ...@@ -5,8 +5,8 @@ require:
inherit_from: .rubocop_todo.yml inherit_from: .rubocop_todo.yml
AllCops: AllCops:
TargetRubyVersion: 2.1 TargetRubyVersion: 2.3
# Cop names are not displayed in offense messages by default. Change behavior # Cop names are not d§splayed in offense messages by default. Change behavior
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names # by overriding DisplayCopNames, or by giving the -D/--display-cop-names
# option. # option.
DisplayCopNames: true DisplayCopNames: true
...@@ -192,6 +192,9 @@ Style/FlipFlop: ...@@ -192,6 +192,9 @@ Style/FlipFlop:
Style/For: Style/For:
Enabled: true Enabled: true
# Checks if there is a magic comment to enforce string literals
Style/FrozenStringLiteralComment:
Enabled: false
# Do not introduce global variables. # Do not introduce global variables.
Style/GlobalVars: Style/GlobalVars:
Enabled: true Enabled: true
......
...@@ -11,8 +11,10 @@ v 8.12.0 (unreleased) ...@@ -11,8 +11,10 @@ v 8.12.0 (unreleased)
- Change merge_error column from string to text type - Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
- Center build stage columns in pipeline overview (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps) - Shorten task status phrase (ClemMakesApps)
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
...@@ -22,6 +24,7 @@ v 8.12.0 (unreleased) ...@@ -22,6 +24,7 @@ v 8.12.0 (unreleased)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel) - Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import - Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps) - Fix inconsistent background color for filter input field (ClemMakesApps)
- Remove prefixes from transition CSS property (ClemMakesApps)
- Add Sentry logging to API calls - Add Sentry logging to API calls
- Add BroadcastMessage API - Add BroadcastMessage API
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
...@@ -29,11 +32,15 @@ v 8.12.0 (unreleased) ...@@ -29,11 +32,15 @@ v 8.12.0 (unreleased)
- Add search to all issue board lists - Add search to all issue board lists
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Use JavaScript tooltips for mentions !5301 (winniehell)
- Fix markdown help references (ClemMakesApps) - Fix markdown help references (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps)
- Fix accessibility and visibility of project list dropdown button !6140
- Added project specific enable/disable setting for LFS !5997 - Added project specific enable/disable setting for LFS !5997
- Don't expose a user's token in the `/api/v3/user` API (!6047) - Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Ability to manage project issues, snippets, wiki, merge requests and builds access level
- Remove inconsistent font weight for sidebar's labels (ClemMakesApps)
- Added tests for diff notes - Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142 - Add a button to download latest successful artifacts for branches and tags !5142
- Remove redundant pipeline tooltips (ClemMakesApps) - Remove redundant pipeline tooltips (ClemMakesApps)
...@@ -44,6 +51,7 @@ v 8.12.0 (unreleased) ...@@ -44,6 +51,7 @@ v 8.12.0 (unreleased)
- Fix branch title trailing space on hover (ClemMakesApps) - Fix branch title trailing space on hover (ClemMakesApps)
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison)
- Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison)
- Order award emoji tooltips in order they were added (EspadaV8)
- Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps) - Fix spacing and vertical alignment on build status icon on commits page (ClemMakesApps)
- Update merge_requests.md with a simpler way to check out a merge request. !5944 - Update merge_requests.md with a simpler way to check out a merge request. !5944
- Fix button missing type (ClemMakesApps) - Fix button missing type (ClemMakesApps)
...@@ -52,9 +60,11 @@ v 8.12.0 (unreleased) ...@@ -52,9 +60,11 @@ v 8.12.0 (unreleased)
- Load branches asynchronously in Cherry Pick and Revert dialogs. - Load branches asynchronously in Cherry Pick and Revert dialogs.
- Add merge request versions !5467 - Add merge request versions !5467
- Change using size to use count and caching it for number of group members. !5935 - Change using size to use count and caching it for number of group members. !5935
- Replace play icon font with svg (ClemMakesApps)
- Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck) - Added 'only_allow_merge_if_build_succeeds' project setting in the API. !5930 (Duck)
- Reduce number of database queries on builds tab - Reduce number of database queries on builds tab
- Capitalize mentioned issue timeline notes (ClemMakesApps) - Capitalize mentioned issue timeline notes (ClemMakesApps)
- Fix inconsistent checkbox alignment (ClemMakesApps)
- Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger) - Use the default branch for displaying the project icon instead of master !5792 (Hannes Rosenögger)
- Adds response mime type to transaction metric action when it's not HTML - Adds response mime type to transaction metric action when it's not HTML
- Fix hover leading space bug in pipeline graph !5980 - Fix hover leading space bug in pipeline graph !5980
...@@ -62,25 +72,35 @@ v 8.12.0 (unreleased) ...@@ -62,25 +72,35 @@ v 8.12.0 (unreleased)
- Fix repository page ui issues - Fix repository page ui issues
- Fixed invisible scroll controls on build page on iPhone - Fixed invisible scroll controls on build page on iPhone
v 8.11.4 (unreleased) v 8.11.5 (unreleased)
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner) - Optimize branch lookups and force a repository reload for Repository#find_branch
- Fix suggested colors options for new labels in the admin area. !6138
v 8.11.4
- Fix resolving conflicts on forks. !6082
- Fix diff commenting on merge requests created prior to 8.10. !6029
- Fix pipelines tab layout regression. !5952
- Fix "Wiki" link not appearing in navigation for projects with external wiki. !6057
- Do not enforce using hash with hidden key in CI configuration. !6079
- Fix hover leading space bug in pipeline graph !5980
- Fix sorting issues by "last updated" doesn't work after import from GitHub - Fix sorting issues by "last updated" doesn't work after import from GitHub
- GitHub importer use default project visibility for non-private projects - GitHub importer use default project visibility for non-private projects
- Creating an issue through our API now emails label subscribers !5720 - Creating an issue through our API now emails label subscribers !5720
- Block concurrent updates for Pipeline - Block concurrent updates for Pipeline
- Fix resolving conflicts on forks - Don't create groups for unallowed users when importing projects
- Fix diff commenting on merge requests created prior to 8.10
- Fix issue boards leak private label names and descriptions - Fix issue boards leak private label names and descriptions
- Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
- Remove gitorious. !5866
v 8.11.3 v 8.11.3
- Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable - Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label - Label list shows all issues (opened or closed) with that label
- Don't show resolve conflicts link before MR status is updated - Don't show resolve conflicts link before MR status is updated
- Fix "Wiki" link not appearing in navigation for projects with external wiki - Fix IE11 fork button bug !5982
- Fix IE11 fork button bug !598
- Don't prevent viewing the MR when git refs for conflicts can't be found on disk - Don't prevent viewing the MR when git refs for conflicts can't be found on disk
- Fix external issue tracker "Issues" link leading to 404s - Fix external issue tracker "Issues" link leading to 404s
- Don't try to show merge conflict resolution info if a merge conflict contains non-UTF-8 characters
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
v 8.11.2 v 8.11.2
- Show "Create Merge Request" widget for push events to fork projects on the source project. !5978 - Show "Create Merge Request" widget for push events to fork projects on the source project. !5978
...@@ -92,8 +112,6 @@ v 8.11.2 ...@@ -92,8 +112,6 @@ v 8.11.2
v 8.11.1 v 8.11.1
- Pulled due to packaging error. - Pulled due to packaging error.
v 8.11.0 (unreleased)
- Fix pipelines tab layout regression (brycepj)
v 8.11.0 v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862 - Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708 - Add test coverage report badge. !5708
......
...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' ...@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem 'gitlab_git', '~> 10.4.7' gem 'gitlab_git', '~> 10.5'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
......
...@@ -279,7 +279,7 @@ GEM ...@@ -279,7 +279,7 @@ GEM
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3) mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_git (10.4.7) gitlab_git (10.5.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -858,7 +858,7 @@ DEPENDENCIES ...@@ -858,7 +858,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
github-markup (~> 1.4) github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_git (~> 10.4.7) gitlab_git (~> 10.5)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2) gollum-lib (~> 4.2)
......
...@@ -20,7 +20,8 @@ ...@@ -20,7 +20,8 @@
data () { data () {
return { return {
scrollOffset: 250, scrollOffset: 250,
filters: Store.state.filters filters: Store.state.filters,
showCount: false
}; };
}, },
watch: { watch: {
...@@ -30,6 +31,15 @@ ...@@ -30,6 +31,15 @@
this.$els.list.scrollTop = 0; this.$els.list.scrollTop = 0;
}, },
deep: true deep: true
},
issues () {
this.$nextTick(() => {
if (this.scrollHeight() > this.listHeight()) {
this.showCount = true;
} else {
this.showCount = false;
}
});
} }
}, },
methods: { methods: {
...@@ -58,6 +68,7 @@ ...@@ -58,6 +68,7 @@
group: 'issues', group: 'issues',
sort: false, sort: false,
disabled: this.disabled, disabled: this.disabled,
filter: '.board-list-count',
onStart: (e) => { onStart: (e) => {
const card = this.$refs.issue[e.oldIndex]; const card = this.$refs.issue[e.oldIndex];
......
...@@ -11,6 +11,7 @@ class List { ...@@ -11,6 +11,7 @@ class List {
this.loading = true; this.loading = true;
this.loadingMore = false; this.loadingMore = false;
this.issues = []; this.issues = [];
this.issuesSize = 0;
if (obj.label) { if (obj.label) {
this.label = new ListLabel(obj.label); this.label = new ListLabel(obj.label);
...@@ -51,7 +52,7 @@ class List { ...@@ -51,7 +52,7 @@ class List {
} }
nextPage () { nextPage () {
if (Math.floor(this.issues.length / 20) === this.page) { if (this.issuesSize > this.issues.length) {
this.page++; this.page++;
return this.getIssues(false); return this.getIssues(false);
...@@ -76,12 +77,13 @@ class List { ...@@ -76,12 +77,13 @@ class List {
.then((resp) => { .then((resp) => {
const data = resp.json(); const data = resp.json();
this.loading = false; this.loading = false;
this.issuesSize = data.size;
if (emptyIssues) { if (emptyIssues) {
this.issues = []; this.issues = [];
} }
this.createIssues(data); this.createIssues(data.issues);
}); });
} }
...@@ -92,6 +94,7 @@ class List { ...@@ -92,6 +94,7 @@ class List {
} }
addIssue (issue, listFrom) { addIssue (issue, listFrom) {
if (!this.findIssue(issue.id)) {
this.issues.push(issue); this.issues.push(issue);
if (this.label) { if (this.label) {
...@@ -99,7 +102,12 @@ class List { ...@@ -99,7 +102,12 @@ class List {
} }
if (listFrom) { if (listFrom) {
gl.boardService.moveIssue(issue.id, listFrom.id, this.id); this.issuesSize++;
gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
.then(() => {
listFrom.getIssues(false);
});
}
} }
} }
...@@ -112,6 +120,7 @@ class List { ...@@ -112,6 +120,7 @@ class List {
const matchesRemove = removeIssue.id === issue.id; const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) { if (matchesRemove) {
this.issuesSize--;
issue.removeLabel(this.label); issue.removeLabel(this.label);
} }
......
...@@ -199,6 +199,7 @@ ...@@ -199,6 +199,7 @@
break; break;
case 'labels': case 'labels':
switch (path[2]) { switch (path[2]) {
case 'new':
case 'edit': case 'edit':
new Labels(); new Labels();
} }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
this.perPage = this.el.data('perPage'); this.perPage = this.el.data('perPage');
this.clearListeners(); this.clearListeners();
this.initBtnListeners(); this.initBtnListeners();
this.initFilters();
} }
Todos.prototype.clearListeners = function() { Todos.prototype.clearListeners = function() {
...@@ -27,6 +28,31 @@ ...@@ -27,6 +28,31 @@
return $('.todo').on('click', this.goToTodoUrl); return $('.todo').on('click', this.goToTodoUrl);
}; };
Todos.prototype.initFilters = function() {
new UsersSelect();
this.initFilterDropdown($('.js-project-search'), 'project_id', ['text']);
this.initFilterDropdown($('.js-type-search'), 'type');
this.initFilterDropdown($('.js-action-search'), 'action_id');
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize());
});
};
Todos.prototype.initFilterDropdown = function($dropdown, fieldName, searchFields) {
$dropdown.glDropdown({
selectable: true,
filterable: searchFields ? true : false,
fieldName: fieldName,
search: { fields: searchFields },
data: $dropdown.data('data'),
clicked: function() {
return $dropdown.closest('form.filter-form').submit();
}
})
};
Todos.prototype.doneClicked = function(e) { Todos.prototype.doneClicked = function(e) {
var $this; var $this;
e.preventDefault(); e.preventDefault();
......
(function() { (global => {
this.User = (function() { global.User = class {
function User(opts) { constructor(opts) {
this.opts = opts; this.opts = opts;
$('.profile-groups-avatars').tooltip({ this.placeProfileAvatarsToTop();
"placement": "top"
});
this.initTabs(); this.initTabs();
$('.hide-project-limit-message').on('click', function(e) { this.hideProjectLimitMessage();
$.cookie('hide_project_limit_message', 'false', { }
path: gon.relative_url_root || '/'
}); placeProfileAvatarsToTop() {
$(this).parents('.project-limit-message').remove(); $('.profile-groups-avatars').tooltip({
return e.preventDefault(); placement: 'top'
}); });
} }
User.prototype.initTabs = function() { initTabs() {
return new UserTabs({ return new UserTabs({
parentEl: '.user-profile', parentEl: '.user-profile',
action: this.opts.action action: this.opts.action
}); });
}; }
return User;
})();
}).call(this); hideProjectLimitMessage() {
$('.hide-project-limit-message').on('click', e => {
e.preventDefault();
const path = gon.relative_url_root || '/';
$.cookie('hide_project_limit_message', 'false', {
path: path
});
$(this).parents('.project-limit-message').remove();
});
}
}
})(window.gl || (window.gl = {}));
...@@ -183,6 +183,13 @@ ...@@ -183,6 +183,13 @@
&.dropdown-menu-user-link { &.dropdown-menu-user-link {
line-height: 16px; line-height: 16px;
} }
.icon-play {
fill: $table-text-gray;
margin-right: 6px;
height: 12px;
width: 11px;
}
} }
.dropdown-header { .dropdown-header {
......
...@@ -136,6 +136,8 @@ header { ...@@ -136,6 +136,8 @@ header {
} }
.title { .title {
position: relative;
padding-right: 20px;
margin: 0; margin: 0;
font-size: 19px; font-size: 19px;
max-width: 400px; max-width: 400px;
...@@ -148,7 +150,11 @@ header { ...@@ -148,7 +150,11 @@ header {
vertical-align: top; vertical-align: top;
white-space: nowrap; white-space: nowrap;
@media (max-width: $screen-sm-max) { @media (min-width: $screen-sm-min) and (max-width: $screen-sm-max) {
max-width: 300px;
}
@media (max-width: $screen-xs-max) {
max-width: 190px; max-width: 190px;
} }
...@@ -160,11 +166,15 @@ header { ...@@ -160,11 +166,15 @@ header {
} }
.dropdown-toggle-caret { .dropdown-toggle-caret {
position: relative; color: $gl-text-color;
top: -2px; border: transparent;
background: transparent;
position: absolute;
right: 3px;
width: 12px; width: 12px;
line-height: 12px; line-height: 19px;
margin-left: 5px; margin-top: (($header-height - 19) / 2);
padding: 0;
font-size: 10px; font-size: 10px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
......
...@@ -9,14 +9,6 @@ ...@@ -9,14 +9,6 @@
border-radius: $radius; border-radius: $radius;
} }
@mixin transition($transition) {
-webkit-transition: $transition;
-moz-transition: $transition;
-ms-transition: $transition;
-o-transition: $transition;
transition: $transition;
}
/** /**
* Prefilled mixins * Prefilled mixins
* Mixins with fixed values * Mixins with fixed values
......
...@@ -151,7 +151,7 @@ ...@@ -151,7 +151,7 @@
background-position: right 0 bottom 6px; background-position: right 0 bottom 6px;
border: 1px solid $input-border; border: 1px solid $input-border;
@include border-radius($border-radius-default); @include border-radius($border-radius-default);
@include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
&:focus { &:focus {
border-color: $input-border-focus; border-color: $input-border-focus;
......
...@@ -142,11 +142,6 @@ ...@@ -142,11 +142,6 @@
} }
} }
.board-header-loading-spinner {
margin-right: 10px;
color: $gray-darkest;
}
.board-inner-container { .board-inner-container {
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
padding: $gl-padding; padding: $gl-padding;
...@@ -279,3 +274,13 @@ ...@@ -279,3 +274,13 @@
width: 210px; width: 210px;
} }
} }
.board-list-count {
padding: 10px 0;
color: $gl-placeholder-color;
font-size: 13px;
> .fa {
margin-right: 5px;
}
}
...@@ -4,8 +4,9 @@ ...@@ -4,8 +4,9 @@
margin: 0; margin: 0;
} }
.fa-play { .icon-play {
font-size: 14px; height: 13px;
width: 12px;
} }
.dropdown-new { .dropdown-new {
......
...@@ -12,6 +12,10 @@ ...@@ -12,6 +12,10 @@
padding-right: 8px; padding-right: 8px;
margin-bottom: 10px; margin-bottom: 10px;
min-width: 15px; min-width: 15px;
.selected_issue {
vertical-align: text-top;
}
} }
.issue-labels { .issue-labels {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
.stage { .stage {
max-width: 90px; max-width: 90px;
width: 90px; width: 90px;
text-align: center;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
...@@ -146,6 +147,7 @@ ...@@ -146,6 +147,7 @@
} }
.stage-cell { .stage-cell {
text-align: center;
svg { svg {
height: 18px; height: 18px;
...@@ -153,10 +155,6 @@ ...@@ -153,10 +155,6 @@
vertical-align: middle; vertical-align: middle;
overflow: visible; overflow: visible;
} }
.light {
width: 3px;
}
} }
.duration, .duration,
...@@ -215,6 +213,13 @@ ...@@ -215,6 +213,13 @@
border-color: $border-white-normal; border-color: $border-white-normal;
} }
} }
.btn {
.icon-play {
height: 13px;
width: 12px;
}
}
} }
} }
...@@ -469,12 +474,16 @@ ...@@ -469,12 +474,16 @@
.pipelines.tab-pane { .pipelines.tab-pane {
.content-list.pipelines { .content-list.pipelines {
overflow: scroll; overflow: auto;
} }
.stage { .stage {
max-width: 60px; max-width: 100px;
width: 60px; width: 100px;
}
.pipeline-actions {
min-width: initial;
} }
} }
......
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
.search-icon { .search-icon {
@extend .fa-search; @extend .fa-search;
@include transition(color .15s); transition: color 0.15s;
-webkit-user-select: none; -webkit-user-select: none;
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
...@@ -125,7 +125,7 @@ ...@@ -125,7 +125,7 @@
} }
.location-badge { .location-badge {
@include transition(all .15s); transition: all 0.15s;
background-color: $location-badge-active-bg; background-color: $location-badge-active-bg;
color: $white-light; color: $white-light;
} }
......
...@@ -37,7 +37,7 @@ class JwtController < ApplicationController ...@@ -37,7 +37,7 @@ class JwtController < ApplicationController
def authenticate_project(login, password) def authenticate_project(login, password)
if login == 'gitlab-ci-token' if login == 'gitlab-ci-token'
Project.find_by(builds_enabled: true, runners_token: password) Project.with_builds_enabled.find_by(runners_token: password)
end end
end end
......
...@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController
end end
def builds_enabled def builds_enabled
return render_404 unless @project.builds_enabled? return render_404 unless @project.feature_available?(:builds, current_user)
end end
end end
...@@ -8,12 +8,15 @@ module Projects ...@@ -8,12 +8,15 @@ module Projects
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
issues = issues.page(params[:page]) issues = issues.page(params[:page])
render json: issues.as_json( render json: {
issues: issues.as_json(
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], methods: [:text_color] } labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
}) }),
size: issues.total_count
}
end end
def update def update
......
...@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController ...@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController
end end
def module_enabled def module_enabled
render_404 unless @project.merge_requests_enabled render_404 unless @project.feature_available?(:merge_requests, current_user)
end end
end end
...@@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
def module_enabled def module_enabled
return render_404 unless @project.issues_enabled && @project.default_issues_tracker? return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
end end
def redirect_to_external_issue_tracker def redirect_to_external_issue_tracker
......
...@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController
protected protected
def module_enabled def module_enabled
unless @project.issues_enabled || @project.merge_requests_enabled unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404 return render_404
end end
end end
......
...@@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def module_enabled def module_enabled
return render_404 unless @project.merge_requests_enabled return render_404 unless @project.feature_available?(:merge_requests, current_user)
end end
def validates_merge_request def validates_merge_request
......
...@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def module_enabled def module_enabled
unless @project.issues_enabled || @project.merge_requests_enabled unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404 return render_404
end end
end end
......
...@@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def module_enabled def module_enabled
return render_404 unless @project.snippets_enabled return render_404 unless @project.feature_available?(:snippets, current_user)
end end
def snippet_params def snippet_params
......
...@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController ...@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController
end end
def project_params def project_params
project_feature_attributes =
{
project_feature_attributes:
[
:issues_access_level, :builds_access_level,
:wiki_access_level, :merge_requests_access_level, :snippets_access_level
]
}
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :runners_token, :name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled, :container_registry_enabled,
:issues_tracker_id, :default_branch, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :lfs_enabled :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
:lfs_enabled, project_feature_attributes
) )
end end
......
...@@ -110,7 +110,7 @@ module ApplicationHelper ...@@ -110,7 +110,7 @@ module ApplicationHelper
project = event.project project = event.project
# Skip if project repo is empty or MR disabled # Skip if project repo is empty or MR disabled
return false unless project && !project.empty_repo? && project.merge_requests_enabled return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user)
# Skip if user already created appropriate MR # Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any? return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
......
...@@ -3,7 +3,7 @@ module CompareHelper ...@@ -3,7 +3,7 @@ module CompareHelper
from.present? && from.present? &&
to.present? && to.present? &&
from != to && from != to &&
project.merge_requests_enabled && project.feature_available?(:merge_requests, current_user) &&
project.repository.branch_names.include?(from) && project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to) project.repository.branch_names.include?(to)
end end
......
...@@ -49,6 +49,19 @@ module IssuablesHelper ...@@ -49,6 +49,19 @@ module IssuablesHelper
end end
end end
def project_dropdown_label(project_id, default_label)
return default_label if project_id.nil?
return "Any project" if project_id == "0"
project = Project.find_by(id: project_id)
if project
project.name_with_namespace
else
default_label
end
end
def milestone_dropdown_label(milestone_title, default_label = "Milestone") def milestone_dropdown_label(milestone_title, default_label = "Milestone")
if milestone_title == Milestone::Upcoming.name if milestone_title == Milestone::Upcoming.name
milestone_title = Milestone::Upcoming.title milestone_title = Milestone::Upcoming.title
......
...@@ -61,7 +61,9 @@ module ProjectsHelper ...@@ -61,7 +61,9 @@ module ProjectsHelper
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
if current_user if current_user
project_link << icon("chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) project_link << button_tag(type: 'button', class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) do
icon("chevron-down")
end
end end
full_title = "#{namespace_link} / #{project_link}".html_safe full_title = "#{namespace_link} / #{project_link}".html_safe
...@@ -412,4 +414,23 @@ module ProjectsHelper ...@@ -412,4 +414,23 @@ module ProjectsHelper
message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]") message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end end
def project_feature_options
{
'Disabled' => ProjectFeature::DISABLED,
'Only team members' => ProjectFeature::PRIVATE,
'Everyone with access' => ProjectFeature::ENABLED
}
end
def project_feature_access_select(field)
# Don't show option "everyone with access" if project is private
options = project_feature_options
level = @project.project_feature.public_send(field)
options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED
options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED)
content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control").html_safe
end
end end
...@@ -78,13 +78,11 @@ module TodosHelper ...@@ -78,13 +78,11 @@ module TodosHelper
end end
def todo_actions_options def todo_actions_options
actions = [ [
OpenStruct.new(id: '', title: 'Any Action'), { id: '', text: 'Any Action' },
OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'), { id: Todo::ASSIGNED, text: 'Assigned' },
OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned') { id: Todo::MENTIONED, text: 'Mentioned' }
] ]
options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
end end
def todo_projects_options def todo_projects_options
...@@ -92,22 +90,28 @@ module TodosHelper ...@@ -92,22 +90,28 @@ module TodosHelper
projects = projects.includes(:namespace) projects = projects.includes(:namespace)
projects = projects.map do |project| projects = projects.map do |project|
OpenStruct.new(id: project.id, title: project.name_with_namespace) { id: project.id, text: project.name_with_namespace }
end end
projects.unshift(OpenStruct.new(id: '', title: 'Any Project')) projects.unshift({ id: '', text: 'Any Project' }).to_json
options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
end end
def todo_types_options def todo_types_options
types = [ [
OpenStruct.new(title: 'Any Type', name: ''), { id: '', text: 'Any Type' },
OpenStruct.new(title: 'Issue', name: 'Issue'), { id: 'Issue', text: 'Issue' },
OpenStruct.new(title: 'Merge Request', name: 'MergeRequest') { id: 'MergeRequest', text: 'Merge Request' }
] ]
end
def todo_actions_dropdown_label(selected_action_id, default_action)
selected_action = todo_actions_options.find { |action| action[:id] == selected_action_id.to_i}
selected_action ? selected_action[:text] : default_action
end
options_from_collection_for_select(types, 'name', 'title', params[:type]) def todo_types_dropdown_label(selected_type, default_type)
selected_type = todo_types_options.find { |type| type[:id] == selected_type && type[:id] != '' }
selected_type ? selected_type[:text] : default_type
end end
private private
......
...@@ -108,15 +108,6 @@ class Commit ...@@ -108,15 +108,6 @@ class Commit
@diff_line_count @diff_line_count
end end
# Returns a string describing the commit for use in a link title
#
# Example
#
# "Commit: Alex Denisov - Project git clone panel"
def link_title
"Commit: #{author_name} - #{title}"
end
# Returns the commits title. # Returns the commits title.
# #
# Usually, the commit title is the first line of the commit message. # Usually, the commit title is the first line of the commit message.
......
...@@ -4,12 +4,10 @@ ...@@ -4,12 +4,10 @@
# #
# range = CommitRange.new('f3f85602...e86e1013', project) # range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false # range.exclude_start? # => false
# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013" # range.to_s # => "f3f85602...e86e1013"
# #
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project) # range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true # range.exclude_start? # => true
# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"} # range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013" # range.to_s # => "f3f85602..e86e1013"
# #
...@@ -109,11 +107,6 @@ class CommitRange ...@@ -109,11 +107,6 @@ class CommitRange
reference reference
end end
# Returns a String for use in a link's title attribute
def reference_title
"Commits #{sha_start} through #{sha_to}"
end
# Return a Hash of parameters for passing to a URL helper # Return a Hash of parameters for passing to a URL helper
# #
# See `namespace_project_compare_url` # See `namespace_project_compare_url`
......
...@@ -2,7 +2,7 @@ module Awardable ...@@ -2,7 +2,7 @@ module Awardable
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
has_many :award_emoji, -> { includes(:user) }, as: :awardable, dependent: :destroy has_many :award_emoji, -> { includes(:user).order(:id) }, as: :awardable, dependent: :destroy
if self < Participable if self < Participable
# By default we always load award_emoji user association # By default we always load award_emoji user association
......
# Makes api V3 compatible with old project features permissions methods
#
# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
# fields to a new table "project_features", support for the old fields is still needed in the API.
module ProjectFeaturesCompatibility
extend ActiveSupport::Concern
def wiki_enabled=(value)
write_feature_attribute(:wiki_access_level, value)
end
def builds_enabled=(value)
write_feature_attribute(:builds_access_level, value)
end
def merge_requests_enabled=(value)
write_feature_attribute(:merge_requests_access_level, value)
end
def issues_enabled=(value)
write_feature_attribute(:issues_access_level, value)
end
def snippets_enabled=(value)
write_feature_attribute(:snippets_access_level, value)
end
private
def write_feature_attribute(field, value)
build_project_feature unless project_feature
access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level)
end
end
...@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base ...@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base
include AfterCommitQueue include AfterCommitQueue
include CaseSensitivity include CaseSensitivity
include TokenAuthenticatable include TokenAuthenticatable
include ProjectFeaturesCompatibility
extend Gitlab::ConfigHelper extend Gitlab::ConfigHelper
UNKNOWN_IMPORT_URL = 'http://unknown.git' UNKNOWN_IMPORT_URL = 'http://unknown.git'
delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
default_value_for :archived, false default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage } default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
after_create :ensure_dir_exist after_create :ensure_dir_exist
after_save :ensure_dir_exist, if: :namespace_id_changed? after_save :ensure_dir_exist, if: :namespace_id_changed?
after_initialize :setup_project_feature
# set last_activity_at to the same as created_at # set last_activity_at to the same as created_at
after_create :set_last_activity_at after_create :set_last_activity_at
...@@ -62,10 +61,10 @@ class Project < ActiveRecord::Base ...@@ -62,10 +61,10 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace belongs_to :namespace
has_one :board, dependent: :destroy
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
has_one :board, dependent: :destroy
# Project services # Project services
has_many :services has_many :services
has_one :campfire_service, dependent: :destroy has_one :campfire_service, dependent: :destroy
...@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base ...@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, dependent: :destroy, as: :source has_many :notification_settings, dependent: :destroy, as: :source
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
...@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base ...@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy has_many :deployments, dependent: :destroy
accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
...@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base ...@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base
length: { within: 0..255 }, length: { within: 0..255 },
format: { with: Gitlab::Regex.project_path_regex, format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message } message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
validates :namespace, presence: true validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id
...@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base ...@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct } scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) } scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') }
scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') }
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') } scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) } scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
...@@ -1121,7 +1123,7 @@ class Project < ActiveRecord::Base ...@@ -1121,7 +1123,7 @@ class Project < ActiveRecord::Base
end end
def enable_ci def enable_ci
self.builds_enabled = true project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end end
def any_runners?(&block) def any_runners?(&block)
...@@ -1288,6 +1290,11 @@ class Project < ActiveRecord::Base ...@@ -1288,6 +1290,11 @@ class Project < ActiveRecord::Base
private private
# Prevents the creation of project_feature record for every project
def setup_project_feature
build_project_feature unless project_feature
end
def default_branch_protected? def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
......
class ProjectFeature < ActiveRecord::Base
# == Project features permissions
#
# Grants access level to project tools
#
# Tools can be enabled only for users, everyone or disabled
# Access control is made only for non private projects
#
# levels:
#
# Disabled: not enabled for anyone
# Private: enabled only for team members
# Enabled: enabled for everyone able to access the project
#
# Permision levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
FEATURES = %i(issues merge_requests wiki snippets builds)
belongs_to :project
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
get_permission(user, public_send("#{feature}_access_level"))
end
def builds_enabled?
return true unless builds_access_level
builds_access_level > DISABLED
end
def wiki_enabled?
return true unless wiki_access_level
wiki_access_level > DISABLED
end
def merge_requests_enabled?
return true unless merge_requests_access_level
merge_requests_access_level > DISABLED
end
private
def get_permission(user, level)
case level
when DISABLED
false
when PRIVATE
user && (project.team.member?(user) || user.admin?)
when ENABLED
true
else
true
end
end
end
...@@ -120,8 +120,21 @@ class Repository ...@@ -120,8 +120,21 @@ class Repository
commits commits
end end
def find_branch(name) def find_branch(name, fresh_repo: true)
raw_repository.branches.find { |branch| branch.name == name } # Since the Repository object may have in-memory index changes, invalidating the memoized Repository object may
# cause unintended side effects. Because finding a branch is a read-only operation, we can safely instantiate
# a new repo here to ensure a consistent state to avoid a libgit2 bug where concurrent access (e.g. via git gc)
# may cause the branch to "disappear" erroneously or have the wrong SHA.
#
# See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
raw_repo =
if fresh_repo
Gitlab::Git::Repository.new(path_to_repo)
else
raw_repository
end
raw_repo.find_branch(name)
end end
def find_tag(name) def find_tag(name)
......
...@@ -433,7 +433,7 @@ class User < ActiveRecord::Base ...@@ -433,7 +433,7 @@ class User < ActiveRecord::Base
# #
# This logic is duplicated from `Ability#project_abilities` into a SQL form. # This logic is duplicated from `Ability#project_abilities` into a SQL form.
def projects_where_can_admin_issues def projects_where_can_admin_issues
authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false) authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end end
def is_admin? def is_admin?
......
...@@ -145,28 +145,28 @@ class ProjectPolicy < BasePolicy ...@@ -145,28 +145,28 @@ class ProjectPolicy < BasePolicy
end end
def disabled_features! def disabled_features!
unless project.issues_enabled unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue)) cannot!(*named_abilities(:issue))
end end
unless project.merge_requests_enabled unless project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:merge_request)) cannot!(*named_abilities(:merge_request))
end end
unless project.issues_enabled || project.merge_requests_enabled unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:label)) cannot!(*named_abilities(:label))
cannot!(*named_abilities(:milestone)) cannot!(*named_abilities(:milestone))
end end
unless project.snippets_enabled unless project.feature_available?(:snippets, user)
cannot!(*named_abilities(:project_snippet)) cannot!(*named_abilities(:project_snippet))
end end
unless project.has_wiki? unless project.feature_available?(:wiki, user) || project.has_external_wiki?
cannot!(*named_abilities(:wiki)) cannot!(*named_abilities(:wiki))
end end
unless project.builds_enabled unless project.feature_available?(:builds, user)
cannot!(*named_abilities(:build)) cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline)) cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment)) cannot!(*named_abilities(:environment))
......
...@@ -8,16 +8,18 @@ module Ci ...@@ -8,16 +8,18 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
builds. builds.
# don't run projects which have not enabled shared runners # don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }). joins(:project).where(projects: { shared_runners_enabled: true }).
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
# this returns builds that are ordered by number of running builds # this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all # we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id"). joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC') order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
else else
# do run projects which are only assigned to this runner (FIFO) # do run projects which are only assigned to this runner (FIFO)
builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC') builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
end end
build = builds.find do |build| build = builds.find do |build|
......
...@@ -31,7 +31,7 @@ module MergeRequests ...@@ -31,7 +31,7 @@ module MergeRequests
def get_branches(changes) def get_branches(changes)
return [] if project.empty_repo? return [] if project.empty_repo?
return [] unless project.merge_requests_enabled return [] unless project.merge_requests_enabled?
changes_list = Gitlab::ChangesList.new(changes) changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change| changes_list.map do |change|
......
...@@ -7,7 +7,6 @@ module Projects ...@@ -7,7 +7,6 @@ module Projects
def execute def execute
forked_from_project_id = params.delete(:forked_from_project_id) forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data) import_data = params.delete(:import_data)
@project = Project.new(params) @project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level # Make sure that the user is allowed to use the specified visibility level
...@@ -81,8 +80,7 @@ module Projects ...@@ -81,8 +80,7 @@ module Projects
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
unless @project.gitlab_project_import? unless @project.gitlab_project_import?
@project.create_wiki if @project.wiki_enabled? @project.create_wiki if @project.feature_available?(:wiki, current_user)
@project.build_missing_services @project.build_missing_services
@project.create_labels @project.create_labels
......
...@@ -8,7 +8,6 @@ module Projects ...@@ -8,7 +8,6 @@ module Projects
name: @project.name, name: @project.name,
path: @project.path, path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled, shared_runners_enabled: @project.shared_runners_enabled,
builds_enabled: @project.builds_enabled,
namespace_id: @params[:namespace].try(:id) || current_user.namespace.id namespace_id: @params[:namespace].try(:id) || current_user.namespace.id
} }
...@@ -17,6 +16,9 @@ module Projects ...@@ -17,6 +16,9 @@ module Projects
end end
new_project = CreateService.new(current_user, new_params).execute new_project = CreateService.new(current_user, new_params).execute
builds_access_level = @project.project_feature.builds_access_level
new_project.project_feature.update_attributes(builds_access_level: builds_access_level)
new_project new_project
end end
......
...@@ -28,21 +28,25 @@ ...@@ -28,21 +28,25 @@
.row-content-block.second-block .row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do = form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline .filter-item.inline
= select_tag('project_id', todo_projects_options, - if params[:project_id].present?
class: 'select2 trigger-submit', include_blank: true, = hidden_field_tag(:project_id, params[:project_id])
data: {placeholder: 'Project'}) = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
placeholder: 'Search projects', data: { data: todo_projects_options } })
.filter-item.inline .filter-item.inline
= users_select_tag(:author_id, selected: params[:author_id], - if params[:author_id].present?
placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) = hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author' } })
.filter-item.inline .filter-item.inline
= select_tag('type', todo_types_options, - if params[:type].present?
class: 'select2 trigger-submit', include_blank: true, = hidden_field_tag(:type, params[:type])
data: {placeholder: 'Type'}) = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
data: { data: todo_types_options } })
.filter-item.inline.actions-filter .filter-item.inline.actions-filter
= select_tag('action_id', todo_actions_options, - if params[:action_id].present?
class: 'select2 trigger-submit', include_blank: true, = hidden_field_tag(:action_id, params[:action_id])
data: {placeholder: 'Action'}) = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
data: { data: todo_actions_options }})
.pull-right .pull-right
.dropdown.inline.prepend-left-10 .dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
...@@ -76,11 +80,3 @@ ...@@ -76,11 +80,3 @@
= paginate @todos, theme: "gitlab" = paginate @todos, theme: "gitlab"
- else - else
.nothing-here-block You're all done! .nothing-here-block You're all done!
:javascript
new UsersSelect();
$('form.filter-form').on('submit', function (event) {
event.preventDefault();
Turbolinks.visit(this.action + '&' + $(this).serialize());
});
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
%span %span
Protected Branches Protected Branches
- if @project.builds_enabled? - if @project.feature_available?(:builds, current_user)
= nav_link(controller: :runners) do = nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
%span %span
......
...@@ -13,14 +13,13 @@ ...@@ -13,14 +13,13 @@
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" } %h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
{{ list.title }} {{ list.title }}
%span.pull-right{ "v-if" => "list.type !== 'blank'" } %span.pull-right{ "v-if" => "list.type !== 'blank'" }
{{ list.issues.length }} {{ list.issuesSize }}
- if can?(current_user, :admin_list, @project) - if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true, %board-delete{ "inline-template" => true,
":list" => "list", ":list" => "list",
"v-if" => "!list.preset && list.id" } "v-if" => "!list.preset && list.id" }
%button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" } %button.board-delete.has-tooltip.pull-right{ type: "button", title: "Delete list", "aria-label" => "Delete list", data: { placement: "bottom" }, "@click.stop" => "deleteBoard" }
= icon("trash") = icon("trash")
= icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore")
%board-list{ "inline-template" => true, %board-list{ "inline-template" => true,
"v-if" => "list.type !== 'blank'", "v-if" => "list.type !== 'blank'",
":list" => "list", ":list" => "list",
...@@ -34,5 +33,11 @@ ...@@ -34,5 +33,11 @@
"v-show" => "!loading", "v-show" => "!loading",
":data-board" => "list.id" } ":data-board" => "list.id" }
= render "projects/boards/components/card" = render "projects/boards/components/card"
%li.board-list-count.text-center{ "v-if" => "showCount" }
= icon("spinner spin", "v-show" => "list.loadingMore" )
%span{ "v-if" => "list.issues.length === list.issuesSize" }
Showing all issues
%span{ "v-else" => true }
Showing {{ list.issues.length }} of {{ list.issuesSize }} issues
- if can?(current_user, :admin_list, @project) - if can?(current_user, :admin_list, @project)
= render "projects/boards/components/blank_state" = render "projects/boards/components/blank_state"
...@@ -89,4 +89,4 @@ ...@@ -89,4 +89,4 @@
= icon('repeat') = icon('repeat')
- elsif build.playable? - elsif build.playable?
= link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do
= icon('play') = custom_icon('icon_play')
...@@ -66,13 +66,13 @@ ...@@ -66,13 +66,13 @@
- if actions.any? - if actions.any?
.btn-group .btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} %a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play") = custom_icon('icon_play')
%b.caret %b.caret
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build| - actions.each do |build|
%li %li
= link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do = link_to play_namespace_project_build_path(pipeline.project.namespace, pipeline.project, build), method: :post, rel: 'nofollow' do
= icon("play") = custom_icon('icon_play')
%span= build.name.humanize %span= build.name.humanize
- if artifacts.present? - if artifacts.present?
.btn-group .btn-group
......
...@@ -5,13 +5,13 @@ ...@@ -5,13 +5,13 @@
.inline .inline
.dropdown .dropdown
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
= icon("play") = custom_icon('icon_play')
%b.caret %b.caret
%ul.dropdown-menu.dropdown-menu-align-right %ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action| - actions.each do |action|
%li %li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do = link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
= icon("play") = custom_icon('icon_play')
%span= action.name.humanize %span= action.name.humanize
- if local_assigns.fetch(:allow_rollback, false) - if local_assigns.fetch(:allow_rollback, false)
......
...@@ -44,42 +44,45 @@ ...@@ -44,42 +44,45 @@
%hr %hr
%fieldset.features.append-bottom-0 %fieldset.features.append-bottom-0
%h5.prepend-top-0 %h5.prepend-top-0
Features Feature Visibility
.form-group
.checkbox = f.fields_for :project_feature do |feature_fields|
= f.label :issues_enabled do .form_group.prepend-top-20
= f.check_box :issues_enabled .row
%strong Issues .col-md-9
%br = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.descr Lightweight issue tracking system for this project %span.help-block Lightweight issue tracking system for this project
.form-group .col-md-3
.checkbox = project_feature_access_select(:issues_access_level)
= f.label :merge_requests_enabled do
= f.check_box :merge_requests_enabled .row
%strong Merge Requests .col-md-9
%br = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
%span.descr Submit changes to be merged upstream %span.help-block Submit changes to be merged upstream
.form-group .col-md-3
.checkbox = project_feature_access_select(:merge_requests_access_level)
= f.label :builds_enabled do
= f.check_box :builds_enabled .row
%strong Builds .col-md-9
%br = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
%span.descr Test and deploy your changes before merge %span.help-block Submit Test and deploy your changes before merge
.form-group .col-md-3
.checkbox = project_feature_access_select(:builds_access_level)
= f.label :wiki_enabled do
= f.check_box :wiki_enabled .row
%strong Wiki .col-md-9
%br = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
%span.descr Pages for project documentation %span.help-block Pages for project documentation
.form-group .col-md-3
.checkbox = project_feature_access_select(:wiki_access_level)
= f.label :snippets_enabled do
= f.check_box :snippets_enabled .row
%strong Snippets .col-md-9
%br = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
%span.descr Share code pastes with others out of git repository %span.help-block Share code pastes with others out of Git repository
.col-md-3
= project_feature_access_select(:snippets_access_level)
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.lfs.enabled && current_user.admin?
.form-group .form-group
.checkbox .checkbox
...@@ -90,6 +93,7 @@ ...@@ -90,6 +93,7 @@
%span.descr %span.descr
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
.form-group .form-group
.checkbox .checkbox
...@@ -98,7 +102,7 @@ ...@@ -98,7 +102,7 @@
%strong Container Registry %strong Container Registry
%br %br
%span.descr Enable Container Registry for this repository %span.descr Enable Container Registry for this repository
%hr
= render 'merge_request_settings', f: f = render 'merge_request_settings', f: f
%hr %hr
%fieldset.features.append-bottom-default %fieldset.features.append-bottom-default
......
%li.build %li.build
.curve
.build-content .build-content
- if subject.target_url - if subject.target_url
- link_to subject.target_url do - link_to subject.target_url do
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
= link_to 'Commits', commits_namespace_project_graph_path = link_to 'Commits', commits_namespace_project_graph_path
= nav_link(action: :languages) do = nav_link(action: :languages) do
= link_to 'Languages', languages_namespace_project_graph_path = link_to 'Languages', languages_namespace_project_graph_path
- if @project.builds_enabled? - if @project.feature_available?(:builds, current_user)
= nav_link(action: :ci) do = nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do = link_to ci_namespace_project_graph_path do
Continuous Integration Continuous Integration
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11"><path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play">
\ No newline at end of file <path fill-rule="evenodd" d="m9.283 6.47l-7.564 4.254c-.949.534-1.719.266-1.719-.576v-9.292c0-.852.756-1.117 1.719-.576l7.564 4.254c.949.534.963 1.392 0 1.934"/>
</svg>
\ No newline at end of file
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
= icon('spinner spin', class: 'block-loading') = icon('spinner spin', class: 'block-loading')
- if can_edit_issuable - if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right' = link_to 'Edit', '#', class: 'edit-link pull-right'
.value.bold.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) } .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
- if issuable.labels_array.any? - if issuable.labels_array.any?
- issuable.labels_array.each do |label| - issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name) = link_to_label(label, type: issuable.to_ability_name)
......
...@@ -123,6 +123,6 @@ ...@@ -123,6 +123,6 @@
:javascript :javascript
var userProfile; var userProfile;
userProfile = new User({ userProfile = new gl.User({
action: "#{controller.action_name}" action: "#{controller.action_name}"
}); });
class CreateProjectFeatures < ActiveRecord::Migration
DOWNTIME = false
def change
create_table :project_features do |t|
t.belongs_to :project, index: true
t.integer :merge_requests_access_level
t.integer :issues_access_level
t.integer :wiki_access_level
t.integer :snippets_access_level
t.integer :builds_access_level
t.timestamps
end
end
end
class MigrateProjectFeatures < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON =
<<-EOT
Migrating issues_enabled, merge_requests_enabled, wiki_enabled, builds_enabled, snippets_enabled fields from projects to
a new table called project_features.
EOT
def up
sql =
%Q{
INSERT INTO project_features(project_id, issues_access_level, merge_requests_access_level, wiki_access_level,
builds_access_level, snippets_access_level, created_at, updated_at)
SELECT
id AS project_id,
CASE WHEN issues_enabled IS true THEN 20 ELSE 0 END AS issues_access_level,
CASE WHEN merge_requests_enabled IS true THEN 20 ELSE 0 END AS merge_requests_access_level,
CASE WHEN wiki_enabled IS true THEN 20 ELSE 0 END AS wiki_access_level,
CASE WHEN builds_enabled IS true THEN 20 ELSE 0 END AS builds_access_level,
CASE WHEN snippets_enabled IS true THEN 20 ELSE 0 END AS snippets_access_level,
created_at,
updated_at
FROM projects
}
execute(sql)
end
def down
sql = %Q{
UPDATE projects
SET
issues_enabled = COALESCE((SELECT CASE WHEN issues_access_level = 20 THEN true ELSE false END AS issues_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
merge_requests_enabled = COALESCE((SELECT CASE WHEN merge_requests_access_level = 20 THEN true ELSE false END AS merge_requests_enabled FROM project_features WHERE project_features.project_id = projects.id),true),
wiki_enabled = COALESCE((SELECT CASE WHEN wiki_access_level = 20 THEN true ELSE false END AS wiki_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
builds_enabled = COALESCE((SELECT CASE WHEN builds_access_level = 20 THEN true ELSE false END AS builds_enabled FROM project_features WHERE project_features.project_id = projects.id), true),
snippets_enabled = COALESCE((SELECT CASE WHEN snippets_access_level = 20 THEN true ELSE false END AS snippets_enabled FROM project_features WHERE project_features.project_id = projects.id),true)
}
execute(sql)
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class RemoveFeaturesEnabledFromProjects < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
# Set this constant to true if this migration requires downtime.
DOWNTIME = true
DOWNTIME_REASON = "Removing fields from database requires downtine."
def up
remove_column :projects, :issues_enabled
remove_column :projects, :merge_requests_enabled
remove_column :projects, :builds_enabled
remove_column :projects, :wiki_enabled
remove_column :projects, :snippets_enabled
end
# Ugly SQL but the only way i found to make it work on both Postgres and Mysql
# It will be slow but it is ok since it is a revert method
def down
add_column_with_default(:projects, :issues_enabled, :boolean, default: true, allow_null: false)
add_column_with_default(:projects, :merge_requests_enabled, :boolean, default: true, allow_null: false)
add_column_with_default(:projects, :builds_enabled, :boolean, default: true, allow_null: false)
add_column_with_default(:projects, :wiki_enabled, :boolean, default: true, allow_null: false)
add_column_with_default(:projects, :snippets_enabled, :boolean, default: true, allow_null: false)
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160830232601) do ActiveRecord::Schema.define(version: 20160831223750) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -766,6 +766,19 @@ ActiveRecord::Schema.define(version: 20160830232601) do ...@@ -766,6 +766,19 @@ ActiveRecord::Schema.define(version: 20160830232601) do
add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree
add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
create_table "project_features", force: :cascade do |t|
t.integer "project_id"
t.integer "merge_requests_access_level"
t.integer "issues_access_level"
t.integer "wiki_access_level"
t.integer "snippets_access_level"
t.integer "builds_access_level"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree
create_table "project_group_links", force: :cascade do |t| create_table "project_group_links", force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.integer "group_id", null: false t.integer "group_id", null: false
...@@ -790,11 +803,7 @@ ActiveRecord::Schema.define(version: 20160830232601) do ...@@ -790,11 +803,7 @@ ActiveRecord::Schema.define(version: 20160830232601) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "creator_id" t.integer "creator_id"
t.boolean "issues_enabled", default: true, null: false
t.boolean "merge_requests_enabled", default: true, null: false
t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id" t.integer "namespace_id"
t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
t.integer "visibility_level", default: 0, null: false t.integer "visibility_level", default: 0, null: false
...@@ -808,7 +817,6 @@ ActiveRecord::Schema.define(version: 20160830232601) do ...@@ -808,7 +817,6 @@ ActiveRecord::Schema.define(version: 20160830232601) do
t.integer "commit_count", default: 0 t.integer "commit_count", default: 0
t.text "import_error" t.text "import_error"
t.integer "ci_id" t.integer "ci_id"
t.boolean "builds_enabled", default: true, null: false
t.boolean "shared_runners_enabled", default: true, null: false t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token" t.string "runners_token"
t.string "build_coverage_regex" t.string "build_coverage_regex"
......
...@@ -68,6 +68,8 @@ Parameters: ...@@ -68,6 +68,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : false, "subscribed" : false,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -135,6 +137,8 @@ Parameters: ...@@ -135,6 +137,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": "9999999999999999999999999999999999999999",
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -238,6 +242,8 @@ Parameters: ...@@ -238,6 +242,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -322,6 +328,8 @@ Parameters: ...@@ -322,6 +328,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 0, "user_notes_count": 0,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -397,6 +405,8 @@ Parameters: ...@@ -397,6 +405,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -499,6 +509,8 @@ Parameters: ...@@ -499,6 +509,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": "9999999999999999999999999999999999999999",
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -569,6 +581,8 @@ Parameters: ...@@ -569,6 +581,8 @@ Parameters:
"merge_when_build_succeeds": true, "merge_when_build_succeeds": true,
"merge_status": "can_be_merged", "merge_status": "can_be_merged",
"subscribed" : true, "subscribed" : true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1, "user_notes_count": 1,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
...@@ -724,7 +738,9 @@ Example response: ...@@ -724,7 +738,9 @@ Example response:
}, },
"merge_when_build_succeeds": false, "merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged", "merge_status": "cannot_be_merged",
"subscribed": true "subscribed": true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null
} }
``` ```
...@@ -798,7 +814,9 @@ Example response: ...@@ -798,7 +814,9 @@ Example response:
}, },
"merge_when_build_succeeds": false, "merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged", "merge_status": "cannot_be_merged",
"subscribed": false "subscribed": false,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null
} }
``` ```
...@@ -891,6 +909,8 @@ Example response: ...@@ -891,6 +909,8 @@ Example response:
"merge_when_build_succeeds": false, "merge_when_build_succeeds": false,
"merge_status": "unchecked", "merge_status": "unchecked",
"subscribed": true, "subscribed": true,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 7, "user_notes_count": 7,
"should_remove_source_branch": true, "should_remove_source_branch": true,
"force_remove_source_branch": false, "force_remove_source_branch": false,
......
...@@ -155,15 +155,30 @@ Inside the document: ...@@ -155,15 +155,30 @@ Inside the document:
- Every piece of documentation that comes with a new feature should declare the - Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a GitLab version that feature got introduced. Right below the heading add a
note: `> Introduced in GitLab 8.3.`. note:
```
> Introduced in GitLab 8.3.
```
- If possible every feature should have a link to the MR that introduced it. - If possible every feature should have a link to the MR that introduced it.
The above note would be then transformed to: The above note would be then transformed to:
`> [Introduced][ce-1242] in GitLab 8.3.`, where
the [link identifier](#links) is named after the repository (CE) and the MR ```
number. > [Introduced][ce-1242] in GitLab 8.3.
- If the feature is only in GitLab EE, don't forget to mention it, like: ```
`> Introduced in GitLab EE 8.3.`. Otherwise, leave
this mention out. , where the [link identifier](#links) is named after the repository (CE) and
the MR number.
- If the feature is only in GitLab Enterprise Edition, don't forget to mention
it, like:
```
> Introduced in GitLab Enterprise Edition 8.3.
```
Otherwise, leave this mention out.
## References ## References
......
...@@ -268,9 +268,9 @@ sudo usermod -aG redis git ...@@ -268,9 +268,9 @@ sudo usermod -aG redis git
### Clone the Source ### Clone the Source
# Clone GitLab repository # Clone GitLab repository
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-12-stable gitlab
**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! **Note:** You can change `8-12-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It ### Configure It
......
...@@ -63,24 +63,24 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ...@@ -63,24 +63,24 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### Memory ### Memory
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! You need at least 4GB of addressable memory (RAM + swap) to install and use GitLab!
The operating system and any other running applications will also be using memory The operating system and any other running applications will also be using memory
so keep in mind that you need at least 2GB available before running GitLab. With so keep in mind that you need at least 4GB available before running GitLab. With
less memory GitLab will give strange errors during the reconfigure run and 500 less memory GitLab will give strange errors during the reconfigure run and 500
errors during usage. errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice. - 1GB RAM + 3GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advice.
- 1GB RAM + 1GB swap supports up to 100 users but it will be very slow - 2GB RAM + 2GB swap supports up to 100 users but it will be very slow
- **2GB RAM** is the **recommended** memory size for all installations and supports up to 100 users - **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
- 4GB RAM supports up to 1,000 users - 8GB RAM supports up to 1,000 users
- 8GB RAM supports up to 2,000 users - 16GB RAM supports up to 2,000 users
- 16GB RAM supports up to 4,000 users - 32GB RAM supports up to 4,000 users
- 32GB RAM supports up to 8,000 users - 64GB RAM supports up to 8,000 users
- 64GB RAM supports up to 16,000 users - 128GB RAM supports up to 16,000 users
- 128GB RAM supports up to 32,000 users - 256GB RAM supports up to 32,000 users
- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/) - More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
We recommend having at least 1GB of swap on your server, even if you currently have We recommend having at least 2GB of swap on your server, even if you currently have
enough available RAM. Having swap will help reduce the chance of errors occurring enough available RAM. Having swap will help reduce the chance of errors occurring
if your available memory changes. if your available memory changes.
...@@ -113,10 +113,8 @@ It's possible to increase the amount of unicorn workers and this will usually he ...@@ -113,10 +113,8 @@ It's possible to increase the amount of unicorn workers and this will usually he
For most instances we recommend using: CPU cores + 1 = unicorn workers. For most instances we recommend using: CPU cores + 1 = unicorn workers.
So for a machine with 2 cores, 3 unicorn workers is ideal. So for a machine with 2 cores, 3 unicorn workers is ideal.
For all machines that have 1GB and up we recommend a minimum of three unicorn workers. For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping. If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping.
To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings).
......
...@@ -82,7 +82,7 @@ GitLab 8.1. ...@@ -82,7 +82,7 @@ GitLab 8.1.
```bash ```bash
cd /home/git/gitlab-workhorse cd /home/git/gitlab-workhorse
sudo -u git -H git fetch --all sudo -u git -H git fetch --all
sudo -u git -H git checkout v0.7.8 sudo -u git -H git checkout v0.7.11
sudo -u git -H make sudo -u git -H make
``` ```
......
...@@ -104,6 +104,15 @@ will find the option to flag the user as external. ...@@ -104,6 +104,15 @@ will find the option to flag the user as external.
By default new users are not set as external users. This behavior can be changed By default new users are not set as external users. This behavior can be changed
by an administrator under **Admin > Application Settings**. by an administrator under **Admin > Application Settings**.
## Project features
Project features like wiki and issues can be hidden from users depending on
which visibility level you select on project settings.
- Disabled: disabled for everyone
- Only team members: only team members will see even if your project is public or internal
- Everyone with access: everyone can see depending on your project visibility level
## GitLab CI ## GitLab CI
GitLab CI permissions rely on the role the user has in GitLab. There are four GitLab CI permissions rely on the role the user has in GitLab. There are four
......
...@@ -22,26 +22,6 @@ Feature: Dashboard Todos ...@@ -22,26 +22,6 @@ Feature: Dashboard Todos
And I mark all todos as done And I mark all todos as done
Then I should see all todos marked as done Then I should see all todos marked as done
@javascript
Scenario: I filter by project
Given I filter by "Enterprise"
Then I should not see todos
@javascript
Scenario: I filter by author
Given I filter by "John Doe"
Then I should not see todos related to "Mary Jane" in the list
@javascript
Scenario: I filter by type
Given I filter by "Issue"
Then I should not see todos related to "Merge Requests" in the list
@javascript
Scenario: I filter by action
Given I filter by "Mentioned"
Then I should not see todos related to "Assignments" in the list
@javascript @javascript
Scenario: I click on a todo row Scenario: I click on a todo row
Given I click on the todo Given I click on the todo
......
...@@ -3,7 +3,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps ...@@ -3,7 +3,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
include SharedPaths include SharedPaths
include SharedProject include SharedProject
include SharedUser include SharedUser
include Select2Helper
step '"John Doe" is a developer of project "Shop"' do step '"John Doe" is a developer of project "Shop"' do
project.team << [john_doe, :developer] project.team << [john_doe, :developer]
...@@ -55,7 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps ...@@ -55,7 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
expect(page).to have_content 'To do 0' expect(page).to have_content 'To do 0'
expect(page).to have_content 'Done 4' expect(page).to have_content 'Done 4'
expect(page).to have_content "You're all done!" expect(page).to have_content "You're all done!"
expect(page).not_to have_link project.name_with_namespace expect('.prepend-top-default').not_to have_link project.name_with_namespace
should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}"
should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}" should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}"
should_not_see_todo "John Doe assigned you issue #{issue.to_reference}" should_not_see_todo "John Doe assigned you issue #{issue.to_reference}"
...@@ -80,19 +79,31 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps ...@@ -80,19 +79,31 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
end end
step 'I filter by "Enterprise"' do step 'I filter by "Enterprise"' do
select2(enterprise.id, from: "#project_id") click_button 'Project'
page.within '.dropdown-menu-project' do
click_link enterprise.name_with_namespace
end
end end
step 'I filter by "John Doe"' do step 'I filter by "John Doe"' do
select2(john_doe.id, from: "#author_id") click_button 'Author'
page.within '.dropdown-menu-author' do
click_link john_doe.username
end
end end
step 'I filter by "Issue"' do step 'I filter by "Issue"' do
select2('Issue', from: "#type") click_button 'Type'
page.within '.dropdown-menu-type' do
click_link 'Issue'
end
end end
step 'I filter by "Mentioned"' do step 'I filter by "Mentioned"' do
select2("#{Todo::MENTIONED}", from: '#action_id') click_button 'Action'
page.within '.dropdown-menu-action' do
click_link 'Mentioned'
end
end end
step 'I should not see todos' do step 'I should not see todos' do
......
...@@ -5,7 +5,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -5,7 +5,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'change project settings' do step 'change project settings' do
fill_in 'project_name_edit', with: 'NewName' fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled' select 'Disabled', from: 'project_project_feature_attributes_issues_access_level'
end end
step 'I save project' do step 'I save project' do
......
...@@ -15,7 +15,7 @@ module SharedProject ...@@ -15,7 +15,7 @@ module SharedProject
# Create a specific project called "Shop" # Create a specific project called "Shop"
step 'I own project "Shop"' do step 'I own project "Shop"' do
@project = Project.find_by(name: "Shop") @project = Project.find_by(name: "Shop")
@project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true) @project ||= create(:project, name: "Shop", namespace: @user.namespace)
@project.team << [@user, :master] @project.team << [@user, :master]
end end
...@@ -41,6 +41,8 @@ module SharedProject ...@@ -41,6 +41,8 @@ module SharedProject
step 'I own project "Forum"' do step 'I own project "Forum"' do
@project = Project.find_by(name: "Forum") @project = Project.find_by(name: "Forum")
@project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project') @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project')
@project.build_project_feature
@project.project_feature.save
@project.team << [@user, :master] @project.team << [@user, :master]
end end
...@@ -95,7 +97,7 @@ module SharedProject ...@@ -95,7 +97,7 @@ module SharedProject
step 'I should see project settings' do step 'I should see project settings' do
expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project) expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
expect(page).to have_content("Project name") expect(page).to have_content("Project name")
expect(page).to have_content("Features") expect(page).to have_content("Feature Visibility")
end end
def current_project def current_project
......
...@@ -76,7 +76,15 @@ module API ...@@ -76,7 +76,15 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_with_namespace expose :name, :name_with_namespace
expose :path, :path_with_namespace expose :path, :path_with_namespace
expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :container_registry_enabled expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:user]) }
expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) }
expose :created_at, :last_activity_at expose :created_at, :last_activity_at
expose :shared_runners_enabled, :lfs_enabled expose :shared_runners_enabled, :lfs_enabled
expose :creator_id expose :creator_id
...@@ -84,7 +92,7 @@ module API ...@@ -84,7 +92,7 @@ module API
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :avatar_url expose :avatar_url
expose :star_count, :forks_count expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.issues_enabled? && project.default_issues_tracker? } expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :public_builds expose :public_builds
expose :shared_with_groups do |project, options| expose :shared_with_groups do |project, options|
...@@ -233,6 +241,8 @@ module API ...@@ -233,6 +241,8 @@ module API
expose :milestone, using: Entities::Milestone expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds expose :merge_when_build_succeeds
expose :merge_status expose :merge_status
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :subscribed do |merge_request, options| expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user]) merge_request.subscribed?(options[:current_user])
end end
......
...@@ -97,7 +97,7 @@ module API ...@@ -97,7 +97,7 @@ module API
group = find_group(params[:id]) group = find_group(params[:id])
projects = GroupProjectsFinder.new(group).execute(current_user) projects = GroupProjectsFinder.new(group).execute(current_user)
projects = paginate projects projects = paginate projects
present projects, with: Entities::Project present projects, with: Entities::Project, user: current_user
end end
# Transfer a project to the Group namespace # Transfer a project to the Group namespace
......
...@@ -51,7 +51,7 @@ module API ...@@ -51,7 +51,7 @@ module API
@projects = current_user.viewable_starred_projects @projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = paginate @projects @projects = paginate @projects
present @projects, with: Entities::Project present @projects, with: Entities::Project, user: current_user
end end
# Get all projects for admin user # Get all projects for admin user
......
...@@ -18,10 +18,6 @@ module Banzai ...@@ -18,10 +18,6 @@ module Banzai
@object_sym ||= object_name.to_sym @object_sym ||= object_name.to_sym
end end
def self.object_class_title
@object_title ||= object_class.name.titleize
end
# Public: Find references in text (like `!123` for merge requests) # Public: Find references in text (like `!123` for merge requests)
# #
# AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches| # AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
...@@ -49,10 +45,6 @@ module Banzai ...@@ -49,10 +45,6 @@ module Banzai
self.class.object_sym self.class.object_sym
end end
def object_class_title
self.class.object_class_title
end
def references_in(*args, &block) def references_in(*args, &block)
self.class.references_in(*args, &block) self.class.references_in(*args, &block)
end end
...@@ -198,7 +190,7 @@ module Banzai ...@@ -198,7 +190,7 @@ module Banzai
end end
def object_link_title(object) def object_link_title(object)
"#{object_class_title}: #{object.title}" object.title
end end
def object_link_text(object, matches) def object_link_text(object, matches)
......
...@@ -35,7 +35,7 @@ module Banzai ...@@ -35,7 +35,7 @@ module Banzai
end end
def object_link_title(range) def object_link_title(range)
range.reference_title nil
end end
end end
end end
......
...@@ -28,10 +28,6 @@ module Banzai ...@@ -28,10 +28,6 @@ module Banzai
only_path: context[:only_path]) only_path: context[:only_path])
end end
def object_link_title(commit)
commit.link_title
end
def object_link_text_extras(object, matches) def object_link_text_extras(object, matches)
extras = super extras = super
......
...@@ -70,6 +70,11 @@ module Banzai ...@@ -70,6 +70,11 @@ module Banzai
def unescape_html_entities(text) def unescape_html_entities(text)
CGI.unescapeHTML(text.to_s) CGI.unescapeHTML(text.to_s)
end end
def object_link_title(object)
# use title of wrapped element instead
nil
end
end end
end end
end end
...@@ -59,6 +59,10 @@ module Banzai ...@@ -59,6 +59,10 @@ module Banzai
html_safe html_safe
end end
end end
def object_link_title(object)
nil
end
end end
end end
end end
...@@ -52,7 +52,7 @@ module Banzai ...@@ -52,7 +52,7 @@ module Banzai
end end
def reference_class(type) def reference_class(type)
"gfm gfm-#{type}" "gfm gfm-#{type} has-tooltip"
end end
# Ensure that a :project key exists in context # Ensure that a :project key exists in context
......
...@@ -168,7 +168,7 @@ module Gitlab ...@@ -168,7 +168,7 @@ module Gitlab
unless project.wiki_enabled? unless project.wiki_enabled?
wiki = WikiFormatter.new(project) wiki = WikiFormatter.new(project)
gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url) gitlab_shell.import_repository(project.repository_storage_path, wiki.path_with_namespace, wiki.import_url)
project.update_attribute(:wiki_enabled, true) project.project.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
end end
rescue Gitlab::Shell::Error => e rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created, # GitHub error message when the wiki repo has not been created,
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
end end
def execute def execute
::Projects::CreateService.new( project = ::Projects::CreateService.new(
current_user, current_user,
name: repo.name, name: repo.name,
path: repo.name, path: repo.name,
...@@ -20,9 +20,15 @@ module Gitlab ...@@ -20,9 +20,15 @@ module Gitlab
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility, visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility,
import_type: "github", import_type: "github",
import_source: repo.full_name, import_source: repo.full_name,
import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@"), import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@")
wiki_enabled: !repo.has_wiki? # If repo has wiki we'll import it later
).execute ).execute
# If repo has wiki we'll import it later
if repo.has_wiki? && project
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
end
project
end end
end end
end end
......
...@@ -39,15 +39,12 @@ project_tree: ...@@ -39,15 +39,12 @@ project_tree:
- :labels - :labels
- milestones: - milestones:
- :events - :events
- :project_feature
# Only include the following attributes for the models specified. # Only include the following attributes for the models specified.
included_attributes: included_attributes:
project: project:
- :description - :description
- :issues_enabled
- :merge_requests_enabled
- :wiki_enabled
- :snippets_enabled
- :visibility_level - :visibility_level
- :archived - :archived
user: user:
......
require 'spec_helper' require 'spec_helper'
describe Projects::SnippetsController do describe Projects::SnippetsController do
let(:project) { create(:project_empty_repo, :public, snippets_enabled: true) } let(:project) { create(:project_empty_repo, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user2) { create(:user) } let(:user2) { create(:user) }
......
...@@ -8,7 +8,6 @@ FactoryGirl.define do ...@@ -8,7 +8,6 @@ FactoryGirl.define do
path { name.downcase.gsub(/\s/, '_') } path { name.downcase.gsub(/\s/, '_') }
namespace namespace
creator creator
snippets_enabled true
trait :public do trait :public do
visibility_level Gitlab::VisibilityLevel::PUBLIC visibility_level Gitlab::VisibilityLevel::PUBLIC
...@@ -27,6 +26,26 @@ FactoryGirl.define do ...@@ -27,6 +26,26 @@ FactoryGirl.define do
project.create_repository project.create_repository
end end
end end
# Nest Project Feature attributes
transient do
wiki_access_level ProjectFeature::ENABLED
builds_access_level ProjectFeature::ENABLED
snippets_access_level ProjectFeature::ENABLED
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
end
after(:create) do |project, evaluator|
project.project_feature.
update_attributes(
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: evaluator.builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: evaluator.merge_requests_access_level,
)
end
end end
# Project with empty repository # Project with empty repository
......
...@@ -182,14 +182,21 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -182,14 +182,21 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource wait_for_vue_resource
page.within(find('.board', match: :first)) do page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('20') expect(page.find('.board-header')).to have_content('56')
expect(page).to have_selector('.card', count: 20) expect(page).to have_selector('.card', count: 20)
expect(page).to have_content('Showing 20 of 56 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
wait_for_vue_resource(spinner: false) wait_for_vue_resource(spinner: false)
expect(page.find('.board-header')).to have_content('40')
expect(page).to have_selector('.card', count: 40) expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 56 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
wait_for_vue_resource(spinner: false)
expect(page).to have_selector('.card', count: 56)
expect(page).to have_content('Showing all issues')
end end
end end
...@@ -479,13 +486,19 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -479,13 +486,19 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource wait_for_vue_resource
page.within(find('.board', match: :first)) do page.within(find('.board', match: :first)) do
expect(page.find('.board-header')).to have_content('20') expect(page.find('.board-header')).to have_content('51')
expect(page).to have_selector('.card', count: 20) expect(page).to have_selector('.card', count: 20)
expect(page).to have_content('Showing 20 of 51 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
expect(page.find('.board-header')).to have_content('40')
expect(page).to have_selector('.card', count: 40) expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 51 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
expect(page).to have_selector('.card', count: 51)
expect(page).to have_content('Showing all issues')
end end
end end
......
require 'spec_helper'
include WaitForAjax
describe 'Edit Project Settings', feature: true do
let(:member) { create(:user) }
let!(:project) { create(:project, :public, path: 'gitlab', name: 'sample') }
let(:non_member) { create(:user) }
describe 'project features visibility selectors', js: true do
before do
project.team << [member, :master]
login_as(member)
end
tools = { builds: "pipelines", issues: "issues", wiki: "wiki", snippets: "snippets", merge_requests: "merge_requests" }
tools.each do |tool_name, shortcut_name|
describe "feature #{tool_name}" do
it 'toggles visibility' do
visit edit_namespace_project_path(project.namespace, project)
select 'Disabled', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
wait_for_ajax
expect(page).not_to have_selector(".shortcuts-#{shortcut_name}")
select 'Everyone with access', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
wait_for_ajax
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
select 'Only team members', from: "project_project_feature_attributes_#{tool_name}_access_level"
click_button 'Save changes'
wait_for_ajax
expect(page).to have_selector(".shortcuts-#{shortcut_name}")
sleep 0.1
end
end
end
end
describe 'project features visibility pages' do
before do
@tools =
{
builds: namespace_project_pipelines_path(project.namespace, project),
issues: namespace_project_issues_path(project.namespace, project),
wiki: namespace_project_wiki_path(project.namespace, project, :home),
snippets: namespace_project_snippets_path(project.namespace, project),
merge_requests: namespace_project_merge_requests_path(project.namespace, project),
}
end
context 'normal user' do
it 'renders 200 if tool is enabled' do
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::ENABLED)
visit url
expect(page.status_code).to eq(200)
end
end
it 'renders 404 if feature is disabled' do
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
visit url
expect(page.status_code).to eq(404)
end
end
it 'renders 404 if feature is enabled only for team members' do
project.team.truncate
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url
expect(page.status_code).to eq(404)
end
end
it 'renders 200 if users is member of group' do
group = create(:group)
project.group = group
project.save
group.add_owner(member)
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url
expect(page.status_code).to eq(200)
end
end
end
context 'admin user' do
before do
non_member.update_attribute(:admin, true)
login_as(non_member)
end
it 'renders 404 if feature is disabled' do
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::DISABLED)
visit url
expect(page.status_code).to eq(404)
end
end
it 'renders 200 if feature is enabled only for team members' do
project.team.truncate
@tools.each do |method_name, url|
project.project_feature.update_attribute("#{method_name}_access_level", ProjectFeature::PRIVATE)
visit url
expect(page.status_code).to eq(200)
end
end
end
end
end
require 'spec_helper'
describe 'Dashboard > User filters todos', feature: true, js: true do
include WaitForAjax
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }
let(:project_1) { create(:empty_project, name: 'project_1') }
let(:project_2) { create(:empty_project, name: 'project_2') }
let(:issue) { create(:issue, title: 'issue', project: project_1) }
let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') }
before do
create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1)
create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2)
project_1.team << [user_1, :developer]
project_2.team << [user_1, :developer]
login_as(user_1)
visit dashboard_todos_path
end
it 'filters by project' do
click_button 'Project'
within '.dropdown-menu-project' do
fill_in 'Search projects', with: project_1.name_with_namespace
click_link project_1.name_with_namespace
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content project_2.name_with_namespace
end
it 'filters by author' do
click_button 'Author'
within '.dropdown-menu-author' do
fill_in 'Search authors', with: user_1.name
click_link user_1.name
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content user_2.name
end
it 'filters by type' do
click_button 'Type'
within '.dropdown-menu-type' do
click_link 'Issue'
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content ' merge request !'
end
it 'filters by action' do
click_button 'Action'
within '.dropdown-menu-action' do
click_link 'Assigned'
end
wait_for_ajax
expect('.prepend-top-default').not_to have_content ' mentioned '
end
end
{ {
"type": "object",
"required" : [
"issues",
"size"
],
"properties" : {
"issues": {
"type": "array", "type": "array",
"items": { "$ref": "issue.json" } "items": { "$ref": "issue.json" }
},
"size": { "type": "integer" }
},
"additionalProperties": false
} }
...@@ -26,12 +26,15 @@ const listObjDuplicate = { ...@@ -26,12 +26,15 @@ const listObjDuplicate = {
const BoardsMockData = { const BoardsMockData = {
'GET': { 'GET': {
'/test/issue-boards/board/lists{/id}/issues': [{ '/test/issue-boards/board/lists{/id}/issues': {
issues: [{
title: 'Testing', title: 'Testing',
iid: 1, iid: 1,
confidential: false, confidential: false,
labels: [] labels: []
}] }],
size: 1
}
}, },
'POST': { 'POST': {
'/test/issue-boards/board/lists{/id}': listObj '/test/issue-boards/board/lists{/id}': listObj
......
...@@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do ...@@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp expect(reference_filter(act).to_html).to eq exp
end end
it 'includes a title attribute' do it 'includes no title attribute' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq range.reference_title expect(doc.css('a').first.attr('title')).to eq ""
end end
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit_range has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes a title attribute' do it 'includes a title attribute' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('title')).to eq commit.link_title expect(doc.css('a').first.attr('title')).to eq commit.title
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
...@@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do ...@@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("See #{reference}") doc = reference_filter("See #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-commit has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -64,7 +64,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do ...@@ -64,7 +64,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = filter("Issue #{reference}") doc = filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
end end
it 'supports an :only_path context' do it 'supports an :only_path context' do
......
...@@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes a title attribute' do it 'includes a title attribute' do
doc = reference_filter("Issue #{reference}") doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Issue: #{issue.title}" expect(doc.css('a').first.attr('title')).to eq issue.title
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
...@@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do ...@@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("Issue #{reference}") doc = reference_filter("Issue #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-issue has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do ...@@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("Label #{reference}") doc = reference_filter("Label #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-label has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do ...@@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes a title attribute' do it 'includes a title attribute' do
doc = reference_filter("Merge #{reference}") doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Merge Request: #{merge.title}" expect(doc.css('a').first.attr('title')).to eq merge.title
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
...@@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do ...@@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("Merge #{reference}") doc = reference_filter("Merge #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-merge_request has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do ...@@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("Milestone #{reference}") doc = reference_filter("Milestone #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
...@@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do ...@@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes a title attribute' do it 'includes a title attribute' do
doc = reference_filter("Snippet #{reference}") doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('title')).to eq "Snippet: #{snippet.title}" expect(doc.css('a').first.attr('title')).to eq snippet.title
end end
it 'escapes the title attribute' do it 'escapes the title attribute' do
...@@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do ...@@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes default classes' do it 'includes default classes' do
doc = reference_filter("Snippet #{reference}") doc = reference_filter("Snippet #{reference}")
expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet' expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-snippet has-tooltip'
end end
it 'includes a data-project attribute' do it 'includes a data-project attribute' do
......
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.
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