summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryce Johnson <bryce@gitlab.com>2016-09-02 13:58:47 +0200
committerBryce Johnson <bryce@gitlab.com>2016-09-02 13:58:47 +0200
commitbaf2cd72b344823b91ef39d0344375e9dbb84208 (patch)
tree4e8e6de0b2c9fe53a54c4096050d3a794edbef84
parente6aa1c5266d10fa13a481492f4a41eba3b5eb0c3 (diff)
parentfaac71215789cb8224c14be0a4fcbe252a99fa4f (diff)
downloadgitlab-ce-baf2cd72b344823b91ef39d0344375e9dbb84208.tar.gz
Merge branch 'master' into repository-page-ui-issues
-rw-r--r--.rubocop.yml7
-rw-r--r--CHANGELOG36
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/boards/components/board_list.js.es613
-rw-r--r--app/assets/javascripts/boards/models/list.js.es625
-rw-r--r--app/assets/javascripts/dispatcher.js1
-rw-r--r--app/assets/javascripts/todos.js26
-rw-r--r--app/assets/javascripts/user.js29
-rw-r--r--app/assets/javascripts/user.js.es634
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss7
-rw-r--r--app/assets/stylesheets/framework/header.scss20
-rw-r--r--app/assets/stylesheets/framework/mixins.scss8
-rw-r--r--app/assets/stylesheets/framework/selects.scss2
-rw-r--r--app/assets/stylesheets/pages/boards.scss15
-rw-r--r--app/assets/stylesheets/pages/environments.scss5
-rw-r--r--app/assets/stylesheets/pages/issues.scss4
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss23
-rw-r--r--app/assets/stylesheets/pages/search.scss4
-rw-r--r--app/controllers/jwt_controller.rb2
-rw-r--r--app/controllers/projects/application_controller.rb2
-rw-r--r--app/controllers/projects/boards/issues_controller.rb15
-rw-r--r--app/controllers/projects/discussions_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/labels_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/milestones_controller.rb2
-rw-r--r--app/controllers/projects/snippets_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb18
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/issuables_helper.rb13
-rw-r--r--app/helpers/projects_helper.rb23
-rw-r--r--app/helpers/todos_helper.rb34
-rw-r--r--app/models/commit.rb9
-rw-r--r--app/models/commit_range.rb7
-rw-r--r--app/models/concerns/awardable.rb2
-rw-r--r--app/models/concerns/project_features_compatibility.rb37
-rw-r--r--app/models/project.rb27
-rw-r--r--app/models/project_feature.rb63
-rw-r--r--app/models/repository.rb17
-rw-r--r--app/models/user.rb2
-rw-r--r--app/policies/project_policy.rb12
-rw-r--r--app/services/ci/register_build_service.rb8
-rw-r--r--app/services/merge_requests/get_urls_service.rb2
-rw-r--r--app/services/projects/create_service.rb4
-rw-r--r--app/services/projects/fork_service.rb4
-rw-r--r--app/views/dashboard/todos/index.html.haml36
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/projects/boards/components/_board.html.haml9
-rw-r--r--app/views/projects/ci/builds/_build.html.haml2
-rw-r--r--app/views/projects/ci/pipelines/_pipeline.html.haml4
-rw-r--r--app/views/projects/deployments/_actions.haml4
-rw-r--r--app/views/projects/edit.html.haml98
-rw-r--r--app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml1
-rw-r--r--app/views/projects/graphs/_head.html.haml2
-rw-r--r--app/views/shared/icons/_icon_play.svg4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--db/migrate/20160831214002_create_project_features.rb16
-rw-r--r--db/migrate/20160831214543_migrate_project_features.rb44
-rw-r--r--db/migrate/20160831223750_remove_features_enabled_from_projects.rb29
-rw-r--r--db/schema.rb20
-rw-r--r--doc/api/merge_requests.md24
-rw-r--r--doc/development/doc_styleguide.md29
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/install/requirements.md30
-rw-r--r--doc/update/8.11-to-8.12.md2
-rw-r--r--doc/user/permissions.md9
-rw-r--r--features/dashboard/todos.feature20
-rw-r--r--features/steps/dashboard/todos.rb23
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/shared/project.rb6
-rw-r--r--lib/api/entities.rb14
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb10
-rw-r--r--lib/banzai/filter/commit_range_reference_filter.rb2
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb4
-rw-r--r--lib/banzai/filter/label_reference_filter.rb5
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb4
-rw-r--r--lib/banzai/filter/reference_filter.rb2
-rw-r--r--lib/gitlab/github_import/importer.rb2
-rw-r--r--lib/gitlab/github_import/project_creator.rb12
-rw-r--r--lib/gitlab/import_export/import_export.yml7
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb2
-rw-r--r--spec/factories/projects.rb21
-rw-r--r--spec/features/boards/boards_spec.rb21
-rw-r--r--spec/features/projects/features_visibility_spec.rb122
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin687442 -> 676870 bytes
-rw-r--r--spec/features/todos/todos_filtering_spec.rb63
-rw-r--r--spec/fixtures/api/schemas/issues.json15
-rw-r--r--spec/javascripts/boards/mock_data.js.es615
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb4
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb3
-rw-r--r--spec/lib/gitlab/github_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.json6
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb13
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb12
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb6
-rw-r--r--spec/models/ability_spec.rb4
-rw-r--r--spec/models/commit_range_spec.rb10
-rw-r--r--spec/models/concerns/awardable_spec.rb10
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb25
-rw-r--r--spec/models/project_feature_spec.rb91
-rw-r--r--spec/models/project_spec.rb4
-rw-r--r--spec/models/repository_spec.rb18
-rw-r--r--spec/models/user_spec.rb3
-rw-r--r--spec/requests/api/merge_requests_spec.rb9
-rw-r--r--spec/requests/api/projects_spec.rb9
-rw-r--r--spec/requests/git_http_spec.rb3
-rw-r--r--spec/requests/jwt_controller_spec.rb13
-rw-r--r--spec/services/ci/register_build_service_spec.rb19
-rw-r--r--spec/services/merge_requests/get_urls_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/workers/repository_check/single_repository_worker_spec.rb8
124 files changed, 1234 insertions, 403 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 282f4539f03..5bd31ccf329 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -5,8 +5,8 @@ require:
inherit_from: .rubocop_todo.yml
AllCops:
- TargetRubyVersion: 2.1
- # Cop names are not displayed in offense messages by default. Change behavior
+ TargetRubyVersion: 2.3
+ # Cop names are not d§splayed in offense messages by default. Change behavior
# by overriding DisplayCopNames, or by giving the -D/--display-cop-names
# option.
DisplayCopNames: true
@@ -192,6 +192,9 @@ Style/FlipFlop:
Style/For:
Enabled: true
+# Checks if there is a magic comment to enforce string literals
+Style/FrozenStringLiteralComment:
+ Enabled: false
# Do not introduce global variables.
Style/GlobalVars:
Enabled: true
diff --git a/CHANGELOG b/CHANGELOG
index 9ace244a847..c249c750ad6 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,8 +11,10 @@ v 8.12.0 (unreleased)
- Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps)
- 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)
- 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)
- Add hover color to emoji icon (ClemMakesApps)
- Fix branches page dropdown sort alignment (ClemMakesApps)
@@ -22,6 +24,7 @@ v 8.12.0 (unreleased)
- Add `wiki_page_events` to project hook APIs (Ben Boeckel)
- Remove Gitorious import
- Fix inconsistent background color for filter input field (ClemMakesApps)
+ - Remove prefixes from transition CSS property (ClemMakesApps)
- Add Sentry logging to API calls
- Add BroadcastMessage API
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
@@ -29,11 +32,15 @@ v 8.12.0 (unreleased)
- Add search to all issue board lists
- Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
+ - Use JavaScript tooltips for mentions !5301 (winniehell)
- Fix markdown help references (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
- Don't expose a user's token in the `/api/v3/user` API (!6047)
- 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
- Add a button to download latest successful artifacts for branches and tags !5142
- Remove redundant pipeline tooltips (ClemMakesApps)
@@ -44,6 +51,7 @@ v 8.12.0 (unreleased)
- Fix branch title trailing space on hover (ClemMakesApps)
- Award emoji tooltips containing more than 10 usernames are now truncated !4780 (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)
- Update merge_requests.md with a simpler way to check out a merge request. !5944
- Fix button missing type (ClemMakesApps)
@@ -52,9 +60,11 @@ v 8.12.0 (unreleased)
- Load branches asynchronously in Cherry Pick and Revert dialogs.
- Add merge request versions !5467
- 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)
- Reduce number of database queries on builds tab
- 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)
- Adds response mime type to transaction metric action when it's not HTML
- Fix hover leading space bug in pipeline graph !5980
@@ -62,25 +72,35 @@ v 8.12.0 (unreleased)
- Fix repository page ui issues
- Fixed invisible scroll controls on build page on iPhone
-v 8.11.4 (unreleased)
- - Fix broken gitlab:backup:restore because of bad permissions on repo storage !6098 (Dirk Hörner)
+v 8.11.5 (unreleased)
+ - 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
- GitHub importer use default project visibility for non-private projects
- Creating an issue through our API now emails label subscribers !5720
- Block concurrent updates for Pipeline
- - Fix resolving conflicts on forks
- - Fix diff commenting on merge requests created prior to 8.10
+ - Don't create groups for unallowed users when importing projects
- 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
- - Do not enforce using hash with hidden key in CI configuration. !6079
- Allow system info page to handle case where info is unavailable
- Label list shows all issues (opened or closed) with that label
- 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 !598
+ - Fix IE11 fork button bug !5982
- 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
+ - 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
- Show "Create Merge Request" widget for push events to fork projects on the source project. !5978
@@ -92,8 +112,6 @@ v 8.11.2
v 8.11.1
- Pulled due to packaging error.
-v 8.11.0 (unreleased)
- - Fix pipelines tab layout regression (brycepj)
v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708
diff --git a/Gemfile b/Gemfile
index 96841013815..620338e5997 100644
--- a/Gemfile
+++ b/Gemfile
@@ -53,7 +53,7 @@ gem 'browser', '~> 2.2'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem 'gitlab_git', '~> 10.4.7'
+gem 'gitlab_git', '~> 10.5'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
diff --git a/Gemfile.lock b/Gemfile.lock
index 1d0fcfd3c3a..28ede86b3ba 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -279,7 +279,7 @@ GEM
diff-lcs (~> 1.1)
mime-types (>= 1.16, < 3)
posix-spawn (~> 0.3)
- gitlab_git (10.4.7)
+ gitlab_git (10.5.0)
activesupport (~> 4.0)
charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0)
@@ -858,7 +858,7 @@ DEPENDENCIES
github-linguist (~> 4.7.0)
github-markup (~> 1.4)
gitlab-flowdock-git-hook (~> 1.0.1)
- gitlab_git (~> 10.4.7)
+ gitlab_git (~> 10.5)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.2)
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js.es6
index a6644e9eb8c..50fc11d7737 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js.es6
@@ -20,7 +20,8 @@
data () {
return {
scrollOffset: 250,
- filters: Store.state.filters
+ filters: Store.state.filters,
+ showCount: false
};
},
watch: {
@@ -30,6 +31,15 @@
this.$els.list.scrollTop = 0;
},
deep: true
+ },
+ issues () {
+ this.$nextTick(() => {
+ if (this.scrollHeight() > this.listHeight()) {
+ this.showCount = true;
+ } else {
+ this.showCount = false;
+ }
+ });
}
},
methods: {
@@ -58,6 +68,7 @@
group: 'issues',
sort: false,
disabled: this.disabled,
+ filter: '.board-list-count',
onStart: (e) => {
const card = this.$refs.issue[e.oldIndex];
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6
index 816fa49516c..91fd620fdb3 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js.es6
@@ -11,6 +11,7 @@ class List {
this.loading = true;
this.loadingMore = false;
this.issues = [];
+ this.issuesSize = 0;
if (obj.label) {
this.label = new ListLabel(obj.label);
@@ -51,7 +52,7 @@ class List {
}
nextPage () {
- if (Math.floor(this.issues.length / 20) === this.page) {
+ if (this.issuesSize > this.issues.length) {
this.page++;
return this.getIssues(false);
@@ -76,12 +77,13 @@ class List {
.then((resp) => {
const data = resp.json();
this.loading = false;
+ this.issuesSize = data.size;
if (emptyIssues) {
this.issues = [];
}
- this.createIssues(data);
+ this.createIssues(data.issues);
});
}
@@ -92,14 +94,20 @@ class List {
}
addIssue (issue, listFrom) {
- this.issues.push(issue);
+ if (!this.findIssue(issue.id)) {
+ this.issues.push(issue);
- if (this.label) {
- issue.addLabel(this.label);
- }
+ if (this.label) {
+ issue.addLabel(this.label);
+ }
- if (listFrom) {
- gl.boardService.moveIssue(issue.id, listFrom.id, this.id);
+ if (listFrom) {
+ this.issuesSize++;
+ gl.boardService.moveIssue(issue.id, listFrom.id, this.id)
+ .then(() => {
+ listFrom.getIssues(false);
+ });
+ }
}
}
@@ -112,6 +120,7 @@ class List {
const matchesRemove = removeIssue.id === issue.id;
if (matchesRemove) {
+ this.issuesSize--;
issue.removeLabel(this.label);
}
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index ba64d2bcf0b..38cdc7b9fba 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -199,6 +199,7 @@
break;
case 'labels':
switch (path[2]) {
+ case 'new':
case 'edit':
new Labels();
}
diff --git a/app/assets/javascripts/todos.js b/app/assets/javascripts/todos.js
index 06605320a35..23eda7d44ca 100644
--- a/app/assets/javascripts/todos.js
+++ b/app/assets/javascripts/todos.js
@@ -13,6 +13,7 @@
this.perPage = this.el.data('perPage');
this.clearListeners();
this.initBtnListeners();
+ this.initFilters();
}
Todos.prototype.clearListeners = function() {
@@ -27,6 +28,31 @@
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) {
var $this;
e.preventDefault();
diff --git a/app/assets/javascripts/user.js b/app/assets/javascripts/user.js
deleted file mode 100644
index 6c4d88cf407..00000000000
--- a/app/assets/javascripts/user.js
+++ /dev/null
@@ -1,29 +0,0 @@
-(function() {
- this.User = (function() {
- function User(opts) {
- this.opts = opts;
- $('.profile-groups-avatars').tooltip({
- "placement": "top"
- });
- this.initTabs();
- $('.hide-project-limit-message').on('click', function(e) {
- $.cookie('hide_project_limit_message', 'false', {
- path: gon.relative_url_root || '/'
- });
- $(this).parents('.project-limit-message').remove();
- return e.preventDefault();
- });
- }
-
- User.prototype.initTabs = function() {
- return new UserTabs({
- parentEl: '.user-profile',
- action: this.opts.action
- });
- };
-
- return User;
-
- })();
-
-}).call(this);
diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js.es6
new file mode 100644
index 00000000000..6889d3a7491
--- /dev/null
+++ b/app/assets/javascripts/user.js.es6
@@ -0,0 +1,34 @@
+(global => {
+ global.User = class {
+ constructor(opts) {
+ this.opts = opts;
+ this.placeProfileAvatarsToTop();
+ this.initTabs();
+ this.hideProjectLimitMessage();
+ }
+
+ placeProfileAvatarsToTop() {
+ $('.profile-groups-avatars').tooltip({
+ placement: 'top'
+ });
+ }
+
+ initTabs() {
+ return new UserTabs({
+ parentEl: '.user-profile',
+ action: this.opts.action
+ });
+ }
+
+ 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 = {}));
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index edb2ff01f88..b0ba112476b 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -183,6 +183,13 @@
&.dropdown-menu-user-link {
line-height: 16px;
}
+
+ .icon-play {
+ fill: $table-text-gray;
+ margin-right: 6px;
+ height: 12px;
+ width: 11px;
+ }
}
.dropdown-header {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 2b4fc0fb068..1036219172e 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -136,6 +136,8 @@ header {
}
.title {
+ position: relative;
+ padding-right: 20px;
margin: 0;
font-size: 19px;
max-width: 400px;
@@ -148,7 +150,11 @@ header {
vertical-align: top;
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;
}
@@ -160,11 +166,15 @@ header {
}
.dropdown-toggle-caret {
- position: relative;
- top: -2px;
+ color: $gl-text-color;
+ border: transparent;
+ background: transparent;
+ position: absolute;
+ right: 3px;
width: 12px;
- line-height: 12px;
- margin-left: 5px;
+ line-height: 19px;
+ margin-top: (($header-height - 19) / 2);
+ padding: 0;
font-size: 10px;
text-align: center;
cursor: pointer;
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 396a37bab6e..62dc2cb3fdc 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -9,14 +9,6 @@
border-radius: $radius;
}
-@mixin transition($transition) {
- -webkit-transition: $transition;
- -moz-transition: $transition;
- -ms-transition: $transition;
- -o-transition: $transition;
- transition: $transition;
-}
-
/**
* Prefilled mixins
* Mixins with fixed values
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index b2e22b60440..c75dacf95d9 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -151,7 +151,7 @@
background-position: right 0 bottom 6px;
border: 1px solid $input-border;
@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 {
border-color: $input-border-focus;
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index d91558bc672..037278bb083 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -142,11 +142,6 @@
}
}
-.board-header-loading-spinner {
- margin-right: 10px;
- color: $gray-darkest;
-}
-
.board-inner-container {
border-bottom: 1px solid $border-color;
padding: $gl-padding;
@@ -279,3 +274,13 @@
width: 210px;
}
}
+
+.board-list-count {
+ padding: 10px 0;
+ color: $gl-placeholder-color;
+ font-size: 13px;
+
+ > .fa {
+ margin-right: 5px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 55f9d4a0011..d01c60ee6ab 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -4,8 +4,9 @@
margin: 0;
}
- .fa-play {
- font-size: 14px;
+ .icon-play {
+ height: 13px;
+ width: 12px;
}
.dropdown-new {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index dfe1e3075da..3a3bb10feac 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -12,6 +12,10 @@
padding-right: 8px;
margin-bottom: 10px;
min-width: 15px;
+
+ .selected_issue {
+ vertical-align: text-top;
+ }
}
.issue-labels {
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 2d6653cd867..ee5d9de66d8 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -2,6 +2,7 @@
.stage {
max-width: 90px;
width: 90px;
+ text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -146,6 +147,7 @@
}
.stage-cell {
+ text-align: center;
svg {
height: 18px;
@@ -153,10 +155,6 @@
vertical-align: middle;
overflow: visible;
}
-
- .light {
- width: 3px;
- }
}
.duration,
@@ -215,6 +213,13 @@
border-color: $border-white-normal;
}
}
+
+ .btn {
+ .icon-play {
+ height: 13px;
+ width: 12px;
+ }
+ }
}
}
@@ -469,12 +474,16 @@
.pipelines.tab-pane {
.content-list.pipelines {
- overflow: scroll;
+ overflow: auto;
}
.stage {
- max-width: 60px;
- width: 60px;
+ max-width: 100px;
+ width: 100px;
+ }
+
+ .pipeline-actions {
+ min-width: initial;
}
}
diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss
index c9d436d72ba..436fb00ba2e 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -80,7 +80,7 @@
.search-icon {
@extend .fa-search;
- @include transition(color .15s);
+ transition: color 0.15s;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -125,7 +125,7 @@
}
.location-badge {
- @include transition(all .15s);
+ transition: all 0.15s;
background-color: $location-badge-active-bg;
color: $white-light;
}
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 014b9b43ff2..66ebdcc37a7 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -37,7 +37,7 @@ class JwtController < ApplicationController
def authenticate_project(login, password)
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
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 91315a07deb..b2ff36f6538 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController
end
def builds_enabled
- return render_404 unless @project.builds_enabled?
+ return render_404 unless @project.feature_available?(:builds, current_user)
end
end
diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb
index 1a4f6b50e8f..9404612a993 100644
--- a/app/controllers/projects/boards/issues_controller.rb
+++ b/app/controllers/projects/boards/issues_controller.rb
@@ -8,12 +8,15 @@ module Projects
issues = ::Boards::Issues::ListService.new(project, current_user, filter_params).execute
issues = issues.page(params[:page])
- render json: issues.as_json(
- only: [:iid, :title, :confidential],
- include: {
- assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
- labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
- })
+ render json: {
+ issues: issues.as_json(
+ only: [:iid, :title, :confidential],
+ include: {
+ assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
+ labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
+ }),
+ size: issues.total_count
+ }
end
def update
diff --git a/app/controllers/projects/discussions_controller.rb b/app/controllers/projects/discussions_controller.rb
index b2e8733ccb7..d174e1145a7 100644
--- a/app/controllers/projects/discussions_controller.rb
+++ b/app/controllers/projects/discussions_controller.rb
@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController
end
def module_enabled
- render_404 unless @project.merge_requests_enabled
+ render_404 unless @project.feature_available?(:merge_requests, current_user)
end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 7c03dcd2e64..72d2d361878 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
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
def redirect_to_external_issue_tracker
diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb
index 0ca675623e5..28fa4a5b141 100644
--- a/app/controllers/projects/labels_controller.rb
+++ b/app/controllers/projects/labels_controller.rb
@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController
protected
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
end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 4f5f3b6aa09..4f9ca0097a1 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.merge_requests_enabled
+ return render_404 unless @project.feature_available?(:merge_requests, current_user)
end
def validates_merge_request
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index da2892bfb3f..ff63f22cb5b 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
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
end
end
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 6d0a7ee1031..17ceefec3b8 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.snippets_enabled
+ return render_404 unless @project.feature_available?(:snippets, current_user)
end
def snippet_params
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 84d6b106cd7..eaa38fa6c98 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController
end
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(
: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,
- :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
- :builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
- :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :lfs_enabled
+ :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
+ :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
+ :public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
+ :lfs_enabled, project_feature_attributes
)
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f3733b01721..5f3765cad0d 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -110,7 +110,7 @@ module ApplicationHelper
project = event.project
# 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
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index f1dc906cab4..aa54ee07bdc 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
- project.merge_requests_enabled &&
+ project.feature_available?(:merge_requests, current_user) &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index b9baeb1d6c4..5c04bba323f 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -49,6 +49,19 @@ module IssuablesHelper
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")
if milestone_title == Milestone::Upcoming.name
milestone_title = Milestone::Upcoming.title
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f07077bd133..d6efa603223 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -61,7 +61,9 @@ module ProjectsHelper
project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" }
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
full_title = "#{namespace_link} / #{project_link}".html_safe
@@ -412,4 +414,23 @@ module ProjectsHelper
message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
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
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 0465327060e..1e86f648203 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -78,13 +78,11 @@ module TodosHelper
end
def todo_actions_options
- actions = [
- OpenStruct.new(id: '', title: 'Any Action'),
- OpenStruct.new(id: Todo::ASSIGNED, title: 'Assigned'),
- OpenStruct.new(id: Todo::MENTIONED, title: 'Mentioned')
+ [
+ { id: '', text: 'Any Action' },
+ { id: Todo::ASSIGNED, text: 'Assigned' },
+ { id: Todo::MENTIONED, text: 'Mentioned' }
]
-
- options_from_collection_for_select(actions, 'id', 'title', params[:action_id])
end
def todo_projects_options
@@ -92,22 +90,28 @@ module TodosHelper
projects = projects.includes(:namespace)
projects = projects.map do |project|
- OpenStruct.new(id: project.id, title: project.name_with_namespace)
+ { id: project.id, text: project.name_with_namespace }
end
- projects.unshift(OpenStruct.new(id: '', title: 'Any Project'))
-
- options_from_collection_for_select(projects, 'id', 'title', params[:project_id])
+ projects.unshift({ id: '', text: 'Any Project' }).to_json
end
def todo_types_options
- types = [
- OpenStruct.new(title: 'Any Type', name: ''),
- OpenStruct.new(title: 'Issue', name: 'Issue'),
- OpenStruct.new(title: 'Merge Request', name: 'MergeRequest')
+ [
+ { id: '', text: 'Any Type' },
+ { id: 'Issue', text: 'Issue' },
+ { 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
private
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 817d063e4a2..e64fd1e0c1b 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -108,15 +108,6 @@ class Commit
@diff_line_count
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.
#
# Usually, the commit title is the first line of the commit message.
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index 630ee9601e0..656a242c265 100644
--- a/app/models/commit_range.rb
+++ b/app/models/commit_range.rb
@@ -4,12 +4,10 @@
#
# range = CommitRange.new('f3f85602...e86e1013', project)
# range.exclude_start? # => false
-# range.reference_title # => "Commits f3f85602 through e86e1013"
# range.to_s # => "f3f85602...e86e1013"
#
# range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
# range.exclude_start? # => true
-# range.reference_title # => "Commits f3f85602^ through e86e1013"
# range.to_param # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
# range.to_s # => "f3f85602..e86e1013"
#
@@ -109,11 +107,6 @@ class CommitRange
reference
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
#
# See `namespace_project_compare_url`
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 83f5bc1fa9e..d8d4575bb4d 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -2,7 +2,7 @@ module Awardable
extend ActiveSupport::Concern
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
# By default we always load award_emoji user association
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
new file mode 100644
index 00000000000..9216122923e
--- /dev/null
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -0,0 +1,37 @@
+# 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
diff --git a/app/models/project.rb b/app/models/project.rb
index e5027af4a0e..a6de2c48071 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base
include AfterCommitQueue
include CaseSensitivity
include TokenAuthenticatable
+ include ProjectFeaturesCompatibility
extend Gitlab::ConfigHelper
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 :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(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
after_create :ensure_dir_exist
after_save :ensure_dir_exist, if: :namespace_id_changed?
+ after_initialize :setup_project_feature
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
@@ -62,10 +61,10 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
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 :board, dependent: :destroy
+
# Project services
has_many :services
has_one :campfire_service, dependent: :destroy
@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, dependent: :destroy, as: :source
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 :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy
accepts_nested_attributes_for :variables, allow_destroy: true
+ accepts_nested_attributes_for :project_feature
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base
length: { within: 0..255 },
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
- validates :issues_enabled, :merge_requests_enabled,
- :wiki_enabled, inclusion: { in: [true, false] }
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
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 :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
@@ -1121,7 +1123,7 @@ class Project < ActiveRecord::Base
end
def enable_ci
- self.builds_enabled = true
+ project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
def any_runners?(&block)
@@ -1288,6 +1290,11 @@ class Project < ActiveRecord::Base
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?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
new file mode 100644
index 00000000000..9c602c582bd
--- /dev/null
+++ b/app/models/project_feature.rb
@@ -0,0 +1,63 @@
+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
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 91bdafdac99..f891e8374d2 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -120,8 +120,21 @@ class Repository
commits
end
- def find_branch(name)
- raw_repository.branches.find { |branch| branch.name == name }
+ def find_branch(name, fresh_repo: true)
+ # 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
def find_tag(name)
diff --git a/app/models/user.rb b/app/models/user.rb
index 8f5958333d7..6996740eebd 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -433,7 +433,7 @@ class User < ActiveRecord::Base
#
# This logic is duplicated from `Ability#project_abilities` into a SQL form.
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
def is_admin?
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 15a9f2f0dca..acf36d422d1 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -145,28 +145,28 @@ class ProjectPolicy < BasePolicy
end
def disabled_features!
- unless project.issues_enabled
+ unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue))
end
- unless project.merge_requests_enabled
+ unless project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:merge_request))
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(:milestone))
end
- unless project.snippets_enabled
+ unless project.feature_available?(:snippets, user)
cannot!(*named_abilities(:project_snippet))
end
- unless project.has_wiki?
+ unless project.feature_available?(:wiki, user) || project.has_external_wiki?
cannot!(*named_abilities(:wiki))
end
- unless project.builds_enabled
+ unless project.feature_available?(:builds, user)
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment))
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 9a187f5d694..6973191b203 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -8,16 +8,18 @@ module Ci
builds =
if current_runner.shared?
builds.
- # don't run projects which have not enabled shared runners
- joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
+ # don't run projects which have not enabled shared runners and builds
+ 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
# 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").
+ 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')
else
# 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
build = builds.find do |build|
diff --git a/app/services/merge_requests/get_urls_service.rb b/app/services/merge_requests/get_urls_service.rb
index 08c1f72d65a..1262ecbc29a 100644
--- a/app/services/merge_requests/get_urls_service.rb
+++ b/app/services/merge_requests/get_urls_service.rb
@@ -31,7 +31,7 @@ module MergeRequests
def get_branches(changes)
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.map do |change|
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 55956be2844..be749ba4a1c 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -7,7 +7,6 @@ module Projects
def execute
forked_from_project_id = params.delete(:forked_from_project_id)
import_data = params.delete(:import_data)
-
@project = Project.new(params)
# Make sure that the user is allowed to use the specified visibility level
@@ -81,8 +80,7 @@ module Projects
log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
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.create_labels
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index de6dc38cc8e..a2de4dccece 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -8,7 +8,6 @@ module Projects
name: @project.name,
path: @project.path,
shared_runners_enabled: @project.shared_runners_enabled,
- builds_enabled: @project.builds_enabled,
namespace_id: @params[:namespace].try(:id) || current_user.namespace.id
}
@@ -17,6 +16,9 @@ module Projects
end
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
end
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 6bcc37042ea..9d31f31c639 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -28,21 +28,25 @@
.row-content-block.second-block
= form_tag todos_filter_path(without: [:project_id, :author_id, :type, :action_id]), method: :get, class: 'filter-form' do
.filter-item.inline
- = select_tag('project_id', todo_projects_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Project'})
+ - if params[:project_id].present?
+ = hidden_field_tag(:project_id, params[:project_id])
+ = 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
- = users_select_tag(:author_id, selected: params[:author_id],
- placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true)
+ - if params[:author_id].present?
+ = 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
- = select_tag('type', todo_types_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Type'})
+ - if params[:type].present?
+ = hidden_field_tag(:type, params[: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
- = select_tag('action_id', todo_actions_options,
- class: 'select2 trigger-submit', include_blank: true,
- data: {placeholder: 'Action'})
-
+ - if params[:action_id].present?
+ = hidden_field_tag(:action_id, params[:action_id])
+ = 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
.dropdown.inline.prepend-left-10
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
@@ -76,11 +80,3 @@
= paginate @todos, theme: "gitlab"
- else
.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());
- });
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 52a5bdc1a1b..613b8b7d301 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -26,7 +26,7 @@
%span
Protected Branches
- - if @project.builds_enabled?
+ - if @project.feature_available?(:builds, current_user)
= nav_link(controller: :runners) do
= link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners' do
%span
diff --git a/app/views/projects/boards/components/_board.html.haml b/app/views/projects/boards/components/_board.html.haml
index 6b4bfe0c354..73066150fb3 100644
--- a/app/views/projects/boards/components/_board.html.haml
+++ b/app/views/projects/boards/components/_board.html.haml
@@ -13,14 +13,13 @@
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
{{ list.title }}
%span.pull-right{ "v-if" => "list.type !== 'blank'" }
- {{ list.issues.length }}
+ {{ list.issuesSize }}
- if can?(current_user, :admin_list, @project)
%board-delete{ "inline-template" => true,
":list" => "list",
"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" }
= icon("trash")
- = icon("spinner spin", class: "board-header-loading-spinner pull-right", "v-show" => "list.loadingMore")
%board-list{ "inline-template" => true,
"v-if" => "list.type !== 'blank'",
":list" => "list",
@@ -34,5 +33,11 @@
"v-show" => "!loading",
":data-board" => "list.id" }
= 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)
= render "projects/boards/components/blank_state"
diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml
index 1fdf32466f2..73de8abe55b 100644
--- a/app/views/projects/ci/builds/_build.html.haml
+++ b/app/views/projects/ci/builds/_build.html.haml
@@ -89,4 +89,4 @@
= icon('repeat')
- 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
- = icon('play')
+ = custom_icon('icon_play')
diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml
index b119f6edf14..bb9493f5158 100644
--- a/app/views/projects/ci/pipelines/_pipeline.html.haml
+++ b/app/views/projects/ci/pipelines/_pipeline.html.haml
@@ -66,13 +66,13 @@
- if actions.any?
.btn-group
%a.dropdown-toggle.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
- = icon("play")
+ = custom_icon('icon_play')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |build|
%li
= 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
- if artifacts.present?
.btn-group
diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml
index f7bf3b834ef..16d134eb6b6 100644
--- a/app/views/projects/deployments/_actions.haml
+++ b/app/views/projects/deployments/_actions.haml
@@ -5,13 +5,13 @@
.inline
.dropdown
%a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'}
- = icon("play")
+ = custom_icon('icon_play')
%b.caret
%ul.dropdown-menu.dropdown-menu-align-right
- actions.each do |action|
%li
= link_to [:play, @project.namespace.becomes(Namespace), @project, action], method: :post, rel: 'nofollow' do
- = icon("play")
+ = custom_icon('icon_play')
%span= action.name.humanize
- if local_assigns.fetch(:allow_rollback, false)
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 836c6d7b83f..f6d751a343e 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -44,52 +44,56 @@
%hr
%fieldset.features.append-bottom-0
%h5.prepend-top-0
- Features
- .form-group
- .checkbox
- = f.label :issues_enabled do
- = f.check_box :issues_enabled
- %strong Issues
- %br
- %span.descr Lightweight issue tracking system for this project
- .form-group
- .checkbox
- = f.label :merge_requests_enabled do
- = f.check_box :merge_requests_enabled
- %strong Merge Requests
- %br
- %span.descr Submit changes to be merged upstream
- .form-group
- .checkbox
- = f.label :builds_enabled do
- = f.check_box :builds_enabled
- %strong Builds
- %br
- %span.descr Test and deploy your changes before merge
- .form-group
- .checkbox
- = f.label :wiki_enabled do
- = f.check_box :wiki_enabled
- %strong Wiki
- %br
- %span.descr Pages for project documentation
- .form-group
- .checkbox
- = f.label :snippets_enabled do
- = f.check_box :snippets_enabled
- %strong Snippets
- %br
- %span.descr Share code pastes with others out of git repository
- - if Gitlab.config.lfs.enabled && current_user.admin?
- .form-group
- .checkbox
- = f.label :lfs_enabled do
- = f.check_box :lfs_enabled, checked: @project.lfs_enabled?
- %strong LFS
- %br
- %span.descr
- Git Large File Storage
- = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+ Feature Visibility
+
+ = f.fields_for :project_feature do |feature_fields|
+ .form_group.prepend-top-20
+ .row
+ .col-md-9
+ = feature_fields.label :issues_access_level, "Issues", class: 'label-light'
+ %span.help-block Lightweight issue tracking system for this project
+ .col-md-3
+ = project_feature_access_select(:issues_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
+ %span.help-block Submit changes to be merged upstream
+ .col-md-3
+ = project_feature_access_select(:merge_requests_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :builds_access_level, "Builds", class: 'label-light'
+ %span.help-block Submit Test and deploy your changes before merge
+ .col-md-3
+ = project_feature_access_select(:builds_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :wiki_access_level, "Wiki", class: 'label-light'
+ %span.help-block Pages for project documentation
+ .col-md-3
+ = project_feature_access_select(:wiki_access_level)
+
+ .row
+ .col-md-9
+ = feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
+ %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?
+ .form-group
+ .checkbox
+ = f.label :lfs_enabled do
+ = f.check_box :lfs_enabled, checked: @project.lfs_enabled?
+ %strong LFS
+ %br
+ %span.descr
+ Git Large File Storage
+ = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
+
- if Gitlab.config.registry.enabled
.form-group
.checkbox
@@ -98,7 +102,7 @@
%strong Container Registry
%br
%span.descr Enable Container Registry for this repository
- %hr
+
= render 'merge_request_settings', f: f
%hr
%fieldset.features.append-bottom-default
diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
index 31d40f6ad03..576d0bec51b 100644
--- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
+++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml
@@ -1,4 +1,5 @@
%li.build
+ .curve
.build-content
- if subject.target_url
- link_to subject.target_url do
diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml
index a231d684559..082e2cb4d8c 100644
--- a/app/views/projects/graphs/_head.html.haml
+++ b/app/views/projects/graphs/_head.html.haml
@@ -12,7 +12,7 @@
= link_to 'Commits', commits_namespace_project_graph_path
= nav_link(action: :languages) do
= link_to 'Languages', languages_namespace_project_graph_path
- - if @project.builds_enabled?
+ - if @project.feature_available?(:builds, current_user)
= nav_link(action: :ci) do
= link_to ci_namespace_project_graph_path do
Continuous Integration
diff --git a/app/views/shared/icons/_icon_play.svg b/app/views/shared/icons/_icon_play.svg
index 80a6d41dbf6..e965afa9a56 100644
--- a/app/views/shared/icons/_icon_play.svg
+++ b/app/views/shared/icons/_icon_play.svg
@@ -1 +1,3 @@
-<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> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 11" class="icon-play">
+ <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
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index c1b50e65af5..b13daaf43c9 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -118,7 +118,7 @@
= icon('spinner spin', class: 'block-loading')
- if can_edit_issuable
= 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?
- issuable.labels_array.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index c7f39868e71..9a052abe40a 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -123,6 +123,6 @@
:javascript
var userProfile;
- userProfile = new User({
+ userProfile = new gl.User({
action: "#{controller.action_name}"
});
diff --git a/db/migrate/20160831214002_create_project_features.rb b/db/migrate/20160831214002_create_project_features.rb
new file mode 100644
index 00000000000..2d76a015a08
--- /dev/null
+++ b/db/migrate/20160831214002_create_project_features.rb
@@ -0,0 +1,16 @@
+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
diff --git a/db/migrate/20160831214543_migrate_project_features.rb b/db/migrate/20160831214543_migrate_project_features.rb
new file mode 100644
index 00000000000..93f9821bc76
--- /dev/null
+++ b/db/migrate/20160831214543_migrate_project_features.rb
@@ -0,0 +1,44 @@
+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
diff --git a/db/migrate/20160831223750_remove_features_enabled_from_projects.rb b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb
new file mode 100644
index 00000000000..a2c207b49ea
--- /dev/null
+++ b/db/migrate/20160831223750_remove_features_enabled_from_projects.rb
@@ -0,0 +1,29 @@
+# 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
diff --git a/db/schema.rb b/db/schema.rb
index 963d528d170..af6e74a4e25 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# 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
enable_extension "plpgsql"
@@ -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", ["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|
t.integer "project_id", null: false
t.integer "group_id", null: false
@@ -790,11 +803,7 @@ ActiveRecord::Schema.define(version: 20160830232601) do
t.datetime "created_at"
t.datetime "updated_at"
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.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at"
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
@@ -808,7 +817,6 @@ ActiveRecord::Schema.define(version: 20160830232601) do
t.integer "commit_count", default: 0
t.text "import_error"
t.integer "ci_id"
- t.boolean "builds_enabled", default: true, null: false
t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token"
t.string "build_coverage_regex"
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index f4760ceac7c..494040a1ce8 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -68,6 +68,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : false,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -135,6 +137,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": "9999999999999999999999999999999999999999",
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -238,6 +242,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -322,6 +328,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 0,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -397,6 +405,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -499,6 +509,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": "9999999999999999999999999999999999999999",
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -569,6 +581,8 @@ Parameters:
"merge_when_build_succeeds": true,
"merge_status": "can_be_merged",
"subscribed" : true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 1,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
@@ -724,7 +738,9 @@ Example response:
},
"merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged",
- "subscribed": true
+ "subscribed": true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null
}
```
@@ -798,7 +814,9 @@ Example response:
},
"merge_when_build_succeeds": false,
"merge_status": "cannot_be_merged",
- "subscribed": false
+ "subscribed": false,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null
}
```
@@ -891,6 +909,8 @@ Example response:
"merge_when_build_succeeds": false,
"merge_status": "unchecked",
"subscribed": true,
+ "sha": "8888888888888888888888888888888888888888",
+ "merge_commit_sha": null,
"user_notes_count": 7,
"should_remove_source_branch": true,
"force_remove_source_branch": false,
diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md
index 37bb59e112c..39b801f761d 100644
--- a/doc/development/doc_styleguide.md
+++ b/doc/development/doc_styleguide.md
@@ -155,15 +155,30 @@ Inside the document:
- 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
- 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.
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.
-- 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.
+
+ ```
+ > [Introduced][ce-1242] in GitLab 8.3.
+ ```
+
+ , 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
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 2d0932d4f04..9522c3e7170 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -268,9 +268,9 @@ sudo usermod -aG redis git
### Clone the Source
# 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
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 571f1a38358..9799e0a3730 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -63,24 +63,24 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
### 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
-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
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 + 1GB 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 supports up to 1,000 users
-- 8GB RAM supports up to 2,000 users
-- 16GB RAM supports up to 4,000 users
-- 32GB RAM supports up to 8,000 users
-- 64GB RAM supports up to 16,000 users
-- 128GB RAM supports up to 32,000 users
+- 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.
+- 2GB RAM + 2GB swap supports up to 100 users but it will be very slow
+- **4GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
+- 8GB RAM supports up to 1,000 users
+- 16GB RAM supports up to 2,000 users
+- 32GB RAM supports up to 4,000 users
+- 64GB RAM supports up to 8,000 users
+- 128GB RAM supports up to 16,000 users
+- 256GB RAM supports up to 32,000 users
- 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
if your available memory changes.
@@ -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.
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.
-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.
-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.
+For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
+If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive 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).
diff --git a/doc/update/8.11-to-8.12.md b/doc/update/8.11-to-8.12.md
index 953e9d7e74c..c8ca42f97bc 100644
--- a/doc/update/8.11-to-8.12.md
+++ b/doc/update/8.11-to-8.12.md
@@ -82,7 +82,7 @@ GitLab 8.1.
```bash
cd /home/git/gitlab-workhorse
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
```
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 66542861781..1498cb361c8 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -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 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 permissions rely on the role the user has in GitLab. There are four
diff --git a/features/dashboard/todos.feature b/features/dashboard/todos.feature
index 42f5d6d2af7..0b23bbb7951 100644
--- a/features/dashboard/todos.feature
+++ b/features/dashboard/todos.feature
@@ -23,26 +23,6 @@ Feature: Dashboard Todos
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
Scenario: I click on a todo row
Given I click on the todo
Then I should be directed to the corresponding page
diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb
index 0607086c166..344b6fda9a6 100644
--- a/features/steps/dashboard/todos.rb
+++ b/features/steps/dashboard/todos.rb
@@ -3,7 +3,6 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
include SharedPaths
include SharedProject
include SharedUser
- include Select2Helper
step '"John Doe" is a developer of project "Shop"' do
project.team << [john_doe, :developer]
@@ -55,7 +54,7 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps
expect(page).to have_content 'To do 0'
expect(page).to have_content 'Done 4'
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 mentioned you on 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
end
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
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
step 'I filter by "Issue"' do
- select2('Issue', from: "#type")
+ click_button 'Type'
+ page.within '.dropdown-menu-type' do
+ click_link 'Issue'
+ end
end
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
step 'I should not see todos' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index 76fefee9254..975c879149e 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -5,7 +5,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'change project settings' do
fill_in 'project_name_edit', with: 'NewName'
- uncheck 'project_issues_enabled'
+ select 'Disabled', from: 'project_project_feature_attributes_issues_access_level'
end
step 'I save project' do
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 0b4920883b8..afbd8ef1233 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -15,7 +15,7 @@ module SharedProject
# Create a specific project called "Shop"
step 'I own project "Shop"' do
@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]
end
@@ -41,6 +41,8 @@ module SharedProject
step 'I own project "Forum"' do
@project = Project.find_by(name: "Forum")
@project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project')
+ @project.build_project_feature
+ @project.project_feature.save
@project.team << [@user, :master]
end
@@ -95,7 +97,7 @@ module SharedProject
step 'I should see project settings' do
expect(current_path).to eq edit_namespace_project_path(@project.namespace, @project)
expect(page).to have_content("Project name")
- expect(page).to have_content("Features")
+ expect(page).to have_content("Feature Visibility")
end
def current_project
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index fe7468dd681..3faba79415b 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -76,7 +76,15 @@ module API
expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group }
expose :name, :name_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 :shared_runners_enabled, :lfs_enabled
expose :creator_id
@@ -84,7 +92,7 @@ module API
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :avatar_url
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 :public_builds
expose :shared_with_groups do |project, options|
@@ -233,6 +241,8 @@ module API
expose :milestone, using: Entities::Milestone
expose :merge_when_build_succeeds
expose :merge_status
+ expose :diff_head_sha, as: :sha
+ expose :merge_commit_sha
expose :subscribed do |merge_request, options|
merge_request.subscribed?(options[:current_user])
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f981ec0dbfe..d2df77238d5 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -97,7 +97,7 @@ module API
group = find_group(params[:id])
projects = GroupProjectsFinder.new(group).execute(current_user)
projects = paginate projects
- present projects, with: Entities::Project
+ present projects, with: Entities::Project, user: current_user
end
# Transfer a project to the Group namespace
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index f8979a1cc29..a1fd598414a 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -51,7 +51,7 @@ module API
@projects = current_user.viewable_starred_projects
@projects = filter_projects(@projects)
@projects = paginate @projects
- present @projects, with: Entities::Project
+ present @projects, with: Entities::Project, user: current_user
end
# Get all projects for admin user
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index d77a5e3ff09..16cd774c81a 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -18,10 +18,6 @@ module Banzai
@object_sym ||= object_name.to_sym
end
- def self.object_class_title
- @object_title ||= object_class.name.titleize
- end
-
# Public: Find references in text (like `!123` for merge requests)
#
# AnyReferenceFilter.references_in(text) do |match, id, project_ref, matches|
@@ -49,10 +45,6 @@ module Banzai
self.class.object_sym
end
- def object_class_title
- self.class.object_class_title
- end
-
def references_in(*args, &block)
self.class.references_in(*args, &block)
end
@@ -198,7 +190,7 @@ module Banzai
end
def object_link_title(object)
- "#{object_class_title}: #{object.title}"
+ object.title
end
def object_link_text(object, matches)
diff --git a/lib/banzai/filter/commit_range_reference_filter.rb b/lib/banzai/filter/commit_range_reference_filter.rb
index bbb88c979cc..4358bf45549 100644
--- a/lib/banzai/filter/commit_range_reference_filter.rb
+++ b/lib/banzai/filter/commit_range_reference_filter.rb
@@ -35,7 +35,7 @@ module Banzai
end
def object_link_title(range)
- range.reference_title
+ nil
end
end
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 2ce1816672b..a26dd09c25a 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -28,10 +28,6 @@ module Banzai
only_path: context[:only_path])
end
- def object_link_title(commit)
- commit.link_title
- end
-
def object_link_text_extras(object, matches)
extras = super
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index e258dc8e2bf..8f262ef3d8d 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -70,6 +70,11 @@ module Banzai
def unescape_html_entities(text)
CGI.unescapeHTML(text.to_s)
end
+
+ def object_link_title(object)
+ # use title of wrapped element instead
+ nil
+ end
end
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index ca686c87d97..58fff496d00 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -59,6 +59,10 @@ module Banzai
html_safe
end
end
+
+ def object_link_title(object)
+ nil
+ end
end
end
end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index bf058241cda..2d221290f7e 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -52,7 +52,7 @@ module Banzai
end
def reference_class(type)
- "gfm gfm-#{type}"
+ "gfm gfm-#{type} has-tooltip"
end
# Ensure that a :project key exists in context
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 1b2a5eb8f52..4fdc2f46be0 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -168,7 +168,7 @@ module Gitlab
unless project.wiki_enabled?
wiki = WikiFormatter.new(project)
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
rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created,
diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb
index 8237fe104d6..e9725880c5e 100644
--- a/lib/gitlab/github_import/project_creator.rb
+++ b/lib/gitlab/github_import/project_creator.rb
@@ -11,7 +11,7 @@ module Gitlab
end
def execute
- ::Projects::CreateService.new(
+ project = ::Projects::CreateService.new(
current_user,
name: repo.name,
path: repo.name,
@@ -20,9 +20,15 @@ module Gitlab
visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : ApplicationSetting.current.default_project_visibility,
import_type: "github",
import_source: repo.full_name,
- 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
+ import_url: repo.clone_url.sub("https://", "https://#{@session_data[:github_access_token]}@")
).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
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 1da51043611..c2e8a1ca5dd 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -39,15 +39,12 @@ project_tree:
- :labels
- milestones:
- :events
+ - :project_feature
# Only include the following attributes for the models specified.
included_attributes:
project:
- :description
- - :issues_enabled
- - :merge_requests_enabled
- - :wiki_enabled
- - :snippets_enabled
- :visibility_level
- :archived
user:
@@ -72,4 +69,4 @@ methods:
statuses:
- :type
merge_request_diff:
- - :utf8_st_diffs \ No newline at end of file
+ - :utf8_st_diffs
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index b8a28f43707..72a3ebf2ebd 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
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(:user2) { create(:user) }
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index f82d68a1816..fb84ba07d25 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -8,7 +8,6 @@ FactoryGirl.define do
path { name.downcase.gsub(/\s/, '_') }
namespace
creator
- snippets_enabled true
trait :public do
visibility_level Gitlab::VisibilityLevel::PUBLIC
@@ -27,6 +26,26 @@ FactoryGirl.define do
project.create_repository
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
# Project with empty repository
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 55e5dc15428..c6c2e2095df 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -182,14 +182,21 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
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_content('Showing 20 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.find('.board-header')).to have_content('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
@@ -479,13 +486,19 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource
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_content('Showing 20 of 51 issues')
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_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
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
new file mode 100644
index 00000000000..9b487e350f2
--- /dev/null
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -0,0 +1,122 @@
+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
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 7bb0d26b21c..e14b2705704 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/todos/todos_filtering_spec.rb b/spec/features/todos/todos_filtering_spec.rb
new file mode 100644
index 00000000000..83cf306437d
--- /dev/null
+++ b/spec/features/todos/todos_filtering_spec.rb
@@ -0,0 +1,63 @@
+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
diff --git a/spec/fixtures/api/schemas/issues.json b/spec/fixtures/api/schemas/issues.json
index 0d2067f704a..70771b21c96 100644
--- a/spec/fixtures/api/schemas/issues.json
+++ b/spec/fixtures/api/schemas/issues.json
@@ -1,4 +1,15 @@
{
- "type": "array",
- "items": { "$ref": "issue.json" }
+ "type": "object",
+ "required" : [
+ "issues",
+ "size"
+ ],
+ "properties" : {
+ "issues": {
+ "type": "array",
+ "items": { "$ref": "issue.json" }
+ },
+ "size": { "type": "integer" }
+ },
+ "additionalProperties": false
}
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6
index 0c37ec8354f..f3797ed44d4 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js.es6
@@ -26,12 +26,15 @@ const listObjDuplicate = {
const BoardsMockData = {
'GET': {
- '/test/issue-boards/board/lists{/id}/issues': [{
- title: 'Testing',
- iid: 1,
- confidential: false,
- labels: []
- }]
+ '/test/issue-boards/board/lists{/id}/issues': {
+ issues: [{
+ title: 'Testing',
+ iid: 1,
+ confidential: false,
+ labels: []
+ }],
+ size: 1
+ }
},
'POST': {
'/test/issue-boards/board/lists{/id}': listObj
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 593bd6d5cac..e6c90ad87ee 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -65,14 +65,14 @@ describe Banzai::Filter::CommitRangeReferenceFilter, lib: true do
expect(reference_filter(act).to_html).to eq exp
end
- it 'includes a title attribute' do
+ it 'includes no title attribute' do
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
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index d46d3f1489e..e0f08282551 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -55,7 +55,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes a title attribute' do
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
it 'escapes the title attribute' do
@@ -67,7 +67,7 @@ describe Banzai::Filter::CommitReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 953466679e4..7116c09fb21 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -64,7 +64,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'supports an :only_path context' do
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index a005b4990e7..fce86a9b6ad 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -54,7 +54,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes a title attribute' do
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
it 'escapes the title attribute' do
@@ -66,7 +66,7 @@ describe Banzai::Filter::IssueReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 9276a154007..908ccebbf87 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -21,7 +21,7 @@ describe Banzai::Filter::LabelReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 805acf1c8b3..274258a045c 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -46,7 +46,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes a title attribute' do
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
it 'escapes the title attribute' do
@@ -58,7 +58,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index 9424f2363e1..7419863d848 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -20,7 +20,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 5068ddd7faa..9b92d1a3926 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -39,7 +39,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes a title attribute' do
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
it 'escapes the title attribute' do
@@ -51,7 +51,7 @@ describe Banzai::Filter::SnippetReferenceFilter, lib: true do
it 'includes default classes' do
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
it 'includes a data-project attribute' do
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 108b36a97cc..fdbdb21eac1 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -104,7 +104,7 @@ describe Banzai::Filter::UserReferenceFilter, lib: true do
it 'includes default classes' do
doc = reference_filter("Hey #{reference}")
- expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member'
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member has-tooltip'
end
it 'supports an :only_path context' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index b0772cad312..7c23e02d05a 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -7,7 +7,8 @@ describe Gitlab::Auth, lib: true do
it 'recognizes CI' do
token = '123'
project = create(:empty_project)
- project.update_attributes(runners_token: token, builds_enabled: true)
+ project.update_attributes(runners_token: token)
+
ip = 'ip'
expect(gl_auth).to receive(:rate_limit!).with(ip, success: true, login: 'gitlab-ci-token')
diff --git a/spec/lib/gitlab/github_import/importer_spec.rb b/spec/lib/gitlab/github_import/importer_spec.rb
index b7c3bc4e1a7..3fb8de81545 100644
--- a/spec/lib/gitlab/github_import/importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::GithubImport::Importer, lib: true do
describe '#execute' do
context 'when an error occurs' do
- let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_enabled: false) }
+ let(:project) { create(:project, import_url: 'https://github.com/octocat/Hello-World.git', wiki_access_level: ProjectFeature::DISABLED) }
let(:octocat) { double(id: 123456, login: 'octocat') }
let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index cbbf98dca94..5114f9c55e1 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -1,9 +1,5 @@
{
"description": "Nisi et repellendus ut enim quo accusamus vel magnam.",
- "issues_enabled": true,
- "merge_requests_enabled": true,
- "wiki_enabled": true,
- "snippets_enabled": false,
"visibility_level": 10,
"archived": false,
"issues": [
@@ -7307,4 +7303,4 @@
"protected_branches": [
]
-} \ No newline at end of file
+}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 4d857945fde..a07ef279e68 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
let(:user) { create(:user) }
let(:namespace) { create(:namespace, owner: user) }
let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
- let(:project) { create(:empty_project, name: 'project', path: 'project') }
+ let!(:project) { create(:empty_project, name: 'project', path: 'project', builds_access_level: ProjectFeature::DISABLED, issues_access_level: ProjectFeature::DISABLED) }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
@@ -18,6 +18,17 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do
expect(restored_project_json).to be true
end
+ it 'restore correct project features' do
+ restored_project_json
+ project = Project.find_by_path('project')
+
+ expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.builds_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.snippets_access_level).to eq(ProjectFeature::ENABLED)
+ expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::ENABLED)
+ expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::ENABLED)
+ end
+
it 'creates a valid pipeline note' do
restored_project_json
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 3a86a4ce07c..d891c2d0cc6 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -111,6 +111,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty
end
+ it 'has project feature' do
+ project_feature = saved_project_json['project_feature']
+ expect(project_feature).not_to be_empty
+ expect(project_feature["issues_access_level"]).to eq(ProjectFeature::DISABLED)
+ expect(project_feature["wiki_access_level"]).to eq(ProjectFeature::ENABLED)
+ expect(project_feature["builds_access_level"]).to eq(ProjectFeature::PRIVATE)
+ end
+
it 'does not complain about non UTF-8 characters in MR diffs' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
@@ -154,6 +162,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do
create(:event, target: milestone, project: project, action: Event::CREATED, author: user)
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
+ project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED)
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::PRIVATE)
+
project
end
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index b6dec41d218..3ceb1e7e803 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -32,6 +32,12 @@ describe Gitlab::ImportExport::Reader, lib: true do
expect(described_class.new(shared: shared).project_tree).to match(include: [:issues])
end
+ it 'generates the correct hash for a single project feature relation' do
+ setup_yaml(project_tree: [:project_feature])
+
+ expect(described_class.new(shared: shared).project_tree).to match(include: [:project_feature])
+ end
+
it 'generates the correct hash for a multiple project relation' do
setup_yaml(project_tree: [:issues, :snippets])
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index b05510342bc..1bdf005c823 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -220,13 +220,13 @@ describe Ability, lib: true do
end
describe '.project_disabled_features_rules' do
- let(:project) { build(:project) }
+ let(:project) { create(:project, wiki_access_level: ProjectFeature::DISABLED) }
subject { described_class.allowed(project.owner, project) }
context 'wiki named abilities' do
it 'disables wiki abilities if the project has no wiki' do
- expect(project).to receive(:has_wiki?).and_return(false)
+ expect(project).to receive(:has_external_wiki?).and_return(false)
expect(subject).not_to include(:read_wiki, :create_wiki, :update_wiki, :admin_wiki)
end
end
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 384a38ebc69..c41359b55a3 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -76,16 +76,6 @@ describe CommitRange, models: true do
end
end
- describe '#reference_title' do
- it 'returns the correct String for three-dot ranges' do
- expect(range.reference_title).to eq "Commits #{full_sha_from} through #{full_sha_to}"
- end
-
- it 'returns the correct String for two-dot ranges' do
- expect(range2.reference_title).to eq "Commits #{full_sha_from}^ through #{full_sha_to}"
- end
- end
-
describe '#to_param' do
it 'includes the correct keys' do
expect(range.to_param.keys).to eq %i(from to)
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index a371c4a18a9..de791abdf3d 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -45,4 +45,14 @@ describe Issue, "Awardable" do
expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1)
end
end
+
+ describe 'querying award_emoji on an Awardable' do
+ let(:issue) { create(:issue) }
+
+ it 'sorts in ascending fashion' do
+ create_list(:award_emoji, 3, awardable: issue)
+
+ expect(issue.award_emoji).to eq issue.award_emoji.sort_by(&:id)
+ end
+ end
end
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
new file mode 100644
index 00000000000..5363aea4d22
--- /dev/null
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe ProjectFeaturesCompatibility do
+ let(:project) { create(:project) }
+ let(:features) { %w(issues wiki builds merge_requests snippets) }
+
+ # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
+ # All those fields got moved to a new table called project_feature and are now integers instead of booleans
+ # This spec tests if the described concern makes sure parameters received by the API are correctly parsed to the new table
+ # So we can keep it compatible
+
+ it "converts fields from 'true' to ProjectFeature::ENABLED" do
+ features.each do |feature|
+ project.update_attribute("#{feature}_enabled".to_sym, "true")
+ expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
+ end
+ end
+
+ it "converts fields from 'false' to ProjectFeature::DISABLED" do
+ features.each do |feature|
+ project.update_attribute("#{feature}_enabled".to_sym, "false")
+ expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
+ end
+ end
+end
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
new file mode 100644
index 00000000000..8d554a01be5
--- /dev/null
+++ b/spec/models/project_feature_spec.rb
@@ -0,0 +1,91 @@
+require 'spec_helper'
+
+describe ProjectFeature do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ describe '#feature_available?' do
+ let(:features) { %w(issues wiki builds merge_requests snippets) }
+
+ context 'when features are disabled' do
+ it "returns false" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED)
+ expect(project.feature_available?(:issues, user)).to eq(false)
+ end
+ end
+ end
+
+ context 'when features are enabled only for team members' do
+ it "returns false when user is not a team member" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+ expect(project.feature_available?(:issues, user)).to eq(false)
+ end
+ end
+
+ it "returns true when user is a team member" do
+ project.team << [user, :developer]
+
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+
+ it "returns true when user is a member of project group" do
+ group = create(:group)
+ project = create(:project, namespace: group)
+ group.add_developer(user)
+
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+
+ it "returns true if user is an admin" do
+ user.update_attribute(:admin, true)
+
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+ end
+
+ context 'when feature is enabled for everyone' do
+ it "returns true" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED)
+ expect(project.feature_available?(:issues, user)).to eq(true)
+ end
+ end
+ end
+ end
+
+ describe '#*_enabled?' do
+ let(:features) { %w(wiki builds merge_requests) }
+
+ it "returns false when feature is disabled" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::DISABLED)
+ expect(project.public_send("#{feature}_enabled?")).to eq(false)
+ end
+ end
+
+ it "returns true when feature is enabled only for team members" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::PRIVATE)
+ expect(project.public_send("#{feature}_enabled?")).to eq(true)
+ end
+ end
+
+ it "returns true when feature is enabled for everyone" do
+ features.each do |feature|
+ project.project_feature.update_attribute("#{feature}_access_level".to_sym, ProjectFeature::ENABLED)
+ expect(project.public_send("#{feature}_enabled?")).to eq(true)
+ end
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index dd309ea1b68..4a41fafb84d 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -508,7 +508,7 @@ describe Project, models: true do
describe '#has_wiki?' do
let(:no_wiki_project) { build(:project, wiki_enabled: false, has_external_wiki: false) }
- let(:wiki_enabled_project) { build(:project, wiki_enabled: true) }
+ let(:wiki_enabled_project) { build(:project) }
let(:external_wiki_project) { build(:project, has_external_wiki: true) }
it 'returns true if project is wiki enabled or has external wiki' do
@@ -734,8 +734,6 @@ describe Project, models: true do
describe '#builds_enabled' do
let(:project) { create :project }
- before { project.builds_enabled = true }
-
subject { project.builds_enabled }
it { expect(project.builds_enabled?).to be_truthy }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 1fea50ad42c..812c72c48cb 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -382,6 +382,24 @@ describe Repository, models: true do
end
end
+ describe '#find_branch' do
+ it 'loads a branch with a fresh repo' do
+ expect(Gitlab::Git::Repository).to receive(:new).twice.and_call_original
+
+ 2.times do
+ expect(repository.find_branch('feature')).not_to be_nil
+ end
+ end
+
+ it 'loads a branch with a cached repo' do
+ expect(Gitlab::Git::Repository).to receive(:new).once.and_call_original
+
+ 2.times do
+ expect(repository.find_branch('feature', fresh_repo: false)).not_to be_nil
+ end
+ end
+ end
+
describe '#rm_branch' do
let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature
let(:blank_sha) { '0000000000000000000000000000000000000000' }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 8eb0c5033c9..a1770d96f83 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1006,8 +1006,7 @@ describe User, models: true do
end
it 'does not include projects for which issues are disabled' do
- project = create(:project)
- project.update_attributes(issues_enabled: false)
+ project = create(:project, issues_access_level: ProjectFeature::DISABLED)
expect(user.projects_where_can_admin_issues.to_a).to be_empty
expect(user.can?(:admin_issue, project)).to eq(false)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index baff872e28e..a7930c59df9 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -9,7 +9,7 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
- let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds) }
+ let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
@@ -34,6 +34,13 @@ describe API::API, api: true do
expect(json_response.length).to eq(3)
expect(json_response.last['title']).to eq(merge_request.title)
expect(json_response.last).to have_key('web_url')
+ expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
+ expect(json_response.last['merge_commit_sha']).to be_nil
+ expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
+ expect(json_response.first['merge_commit_sha']).not_to be_nil
+ expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
end
it "returns an array of all merge_requests" do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 63f2467be63..28aa56e8644 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -73,7 +73,7 @@ describe API::API, api: true do
end
it 'does not include open_issues_count' do
- project.update_attributes( { issues_enabled: false } )
+ project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED)
get api('/projects', user)
expect(response.status).to eq 200
@@ -231,8 +231,15 @@ describe API::API, api: true do
post api('/projects', user), project
project.each_pair do |k, v|
+ next if %i{ issues_enabled merge_requests_enabled wiki_enabled }.include?(k)
expect(json_response[k.to_s]).to eq(v)
end
+
+ # Check feature permissions attributes
+ project = Project.find_by_path(project[:path])
+ expect(project.project_feature.issues_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.merge_requests_access_level).to eq(ProjectFeature::DISABLED)
+ expect(project.project_feature.wiki_access_level).to eq(ProjectFeature::DISABLED)
end
it 'sets a project as public' do
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index afaf4b7cefb..9ca3b021aa2 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -289,7 +289,8 @@ describe 'Git HTTP requests', lib: true do
let(:project) { FactoryGirl.create :empty_project }
before do
- project.update_attributes(runners_token: token, builds_enabled: true)
+ project.update_attributes(runners_token: token)
+ project.project_feature.update_attributes(builds_access_level: ProjectFeature::ENABLED)
end
it "downloads get status 200" do
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index c6172b9cc7d..fc42b534dca 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -22,19 +22,20 @@ describe JwtController do
context 'when using authorized request' do
context 'using CI token' do
- let(:project) { create(:empty_project, runners_token: 'token', builds_enabled: builds_enabled) }
+ let(:project) { create(:empty_project, runners_token: 'token') }
let(:headers) { { authorization: credentials('gitlab-ci-token', project.runners_token) } }
- subject! { get '/jwt/auth', parameters, headers }
-
context 'project with enabled CI' do
- let(:builds_enabled) { true }
-
+ subject! { get '/jwt/auth', parameters, headers }
it { expect(service_class).to have_received(:new).with(project, nil, parameters) }
end
context 'project with disabled CI' do
- let(:builds_enabled) { false }
+ before do
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+ end
+
+ subject! { get '/jwt/auth', parameters, headers }
it { expect(response).to have_http_status(403) }
end
diff --git a/spec/services/ci/register_build_service_spec.rb b/spec/services/ci/register_build_service_spec.rb
index 026d0ca6534..1e21a32a062 100644
--- a/spec/services/ci/register_build_service_spec.rb
+++ b/spec/services/ci/register_build_service_spec.rb
@@ -151,6 +151,25 @@ module Ci
it { expect(build.runner).to eq(specific_runner) }
end
end
+
+ context 'disallow when builds are disabled' do
+ before do
+ project.update(shared_runners_enabled: true)
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
+ end
+
+ context 'and uses shared runner' do
+ let(:build) { service.execute(shared_runner) }
+
+ it { expect(build).to be_nil }
+ end
+
+ context 'and uses specific runner' do
+ let(:build) { service.execute(specific_runner) }
+
+ it { expect(build).to be_nil }
+ end
+ end
end
end
end
diff --git a/spec/services/merge_requests/get_urls_service_spec.rb b/spec/services/merge_requests/get_urls_service_spec.rb
index 8a4b76367e3..3a71776e81f 100644
--- a/spec/services/merge_requests/get_urls_service_spec.rb
+++ b/spec/services/merge_requests/get_urls_service_spec.rb
@@ -50,7 +50,7 @@ describe MergeRequests::GetUrlsService do
let(:changes) { new_branch_changes }
before do
- project.merge_requests_enabled = false
+ project.project_feature.update_attribute(:merge_requests_access_level, ProjectFeature::DISABLED)
end
it_behaves_like 'no_merge_request_url'
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index bbced59ff02..3ea1273abc3 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -69,7 +69,7 @@ describe Projects::CreateService, services: true do
context 'wiki_enabled false does not create wiki repository directory' do
before do
- @opts.merge!(wiki_enabled: false)
+ @opts.merge!( { project_feature_attributes: { wiki_access_level: ProjectFeature::DISABLED } })
@project = create_project(@user, @opts)
@path = ProjectWiki.new(@project, @user).send(:path_to_repo)
end
@@ -85,7 +85,7 @@ describe Projects::CreateService, services: true do
context 'global builds_enabled false does not enable CI by default' do
before do
- @opts.merge!(builds_enabled: false)
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::DISABLED)
end
it { is_expected.to be_falsey }
@@ -93,7 +93,7 @@ describe Projects::CreateService, services: true do
context 'global builds_enabled true does enable CI by default' do
before do
- @opts.merge!(builds_enabled: true)
+ project.project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
it { is_expected.to be_truthy }
diff --git a/spec/workers/repository_check/single_repository_worker_spec.rb b/spec/workers/repository_check/single_repository_worker_spec.rb
index 05e07789dac..59cfb2c8e3a 100644
--- a/spec/workers/repository_check/single_repository_worker_spec.rb
+++ b/spec/workers/repository_check/single_repository_worker_spec.rb
@@ -5,7 +5,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
subject { described_class.new }
it 'passes when the project has no push events' do
- project = create(:project_empty_repo, wiki_enabled: false)
+ project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED)
project.events.destroy_all
break_repo(project)
@@ -25,7 +25,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
it 'fails if the wiki repository is broken' do
- project = create(:project_empty_repo, wiki_enabled: true)
+ project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED)
project.create_wiki
# Test sanity: everything should be fine before the wiki repo is broken
@@ -39,7 +39,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
it 'skips wikis when disabled' do
- project = create(:project_empty_repo, wiki_enabled: false)
+ project = create(:project_empty_repo, wiki_access_level: ProjectFeature::DISABLED)
# Make sure the test would fail if the wiki repo was checked
break_wiki(project)
@@ -49,7 +49,7 @@ describe RepositoryCheck::SingleRepositoryWorker do
end
it 'creates missing wikis' do
- project = create(:project_empty_repo, wiki_enabled: true)
+ project = create(:project_empty_repo, wiki_access_level: ProjectFeature::ENABLED)
FileUtils.rm_rf(wiki_path(project))
subject.perform(project.id)