Commit c1d6cfc3 authored by Bryce Johnson's avatar Bryce Johnson

Merge branch 'master' into merge-request-push-compare-ui

parents 0fe2d331 d38499b3
...@@ -767,26 +767,33 @@ Rails/ScopeArgs: ...@@ -767,26 +767,33 @@ Rails/ScopeArgs:
RSpec/AnyInstance: RSpec/AnyInstance:
Enabled: false Enabled: false
# Check that the first argument to the top level describe is the tested class or # Check for expectations where `be(...)` can replace `eql(...)`.
# module. RSpec/BeEql:
Enabled: false
# Check that the first argument to the top level describe is a constant.
RSpec/DescribeClass: RSpec/DescribeClass:
Enabled: false Enabled: false
# Use `described_class` for tested class / module. # Checks that tests use `described_class`.
RSpec/DescribedClass:
Enabled: false
# Checks that the second argument to `describe` specifies a method.
RSpec/DescribeMethod: RSpec/DescribeMethod:
Enabled: false Enabled: false
# Checks that the second argument to top level describe is the tested method # Checks if an example group does not include any tests.
# name. RSpec/EmptyExampleGroup:
RSpec/DescribedClass:
Enabled: false Enabled: false
CustomIncludeMethods: []
# Checks for long example. # Checks for long examples.
RSpec/ExampleLength: RSpec/ExampleLength:
Enabled: false Enabled: false
Max: 5 Max: 5
# Do not use should when describing your tests. # Checks that example descriptions do not start with "should".
RSpec/ExampleWording: RSpec/ExampleWording:
Enabled: false Enabled: false
CustomTransform: CustomTransform:
...@@ -795,6 +802,10 @@ RSpec/ExampleWording: ...@@ -795,6 +802,10 @@ RSpec/ExampleWording:
not: does not not: does not
IgnoredWords: [] IgnoredWords: []
# Checks for `expect(...)` calls containing literal values.
RSpec/ExpectActual:
Enabled: false
# Checks the file and folder naming of the spec file. # Checks the file and folder naming of the spec file.
RSpec/FilePath: RSpec/FilePath:
Enabled: false Enabled: false
...@@ -806,19 +817,65 @@ RSpec/FilePath: ...@@ -806,19 +817,65 @@ RSpec/FilePath:
RSpec/Focus: RSpec/Focus:
Enabled: true Enabled: true
# Checks the arguments passed to `before`, `around`, and `after`.
RSpec/HookArgument:
Enabled: false
EnforcedStyle: implicit
# Check that a consistent implict expectation style is used.
# TODO (rspeicher): Available in rubocop-rspec 1.8.0
# RSpec/ImplicitExpect:
# Enabled: true
# EnforcedStyle: is_expected
# Checks for the usage of instance variables. # Checks for the usage of instance variables.
RSpec/InstanceVariable: RSpec/InstanceVariable:
Enabled: false Enabled: false
# Checks for multiple top-level describes. # Checks for `subject` definitions that come after `let` definitions.
RSpec/LeadingSubject:
Enabled: false
# Checks unreferenced `let!` calls being used for test setup.
RSpec/LetSetup:
Enabled: false
# Check that chains of messages are not being stubbed.
RSpec/MessageChain:
Enabled: false
# Checks for consistent message expectation style.
RSpec/MessageExpectation:
Enabled: false
EnforcedStyle: allow
# Checks for multiple top level describes.
RSpec/MultipleDescribes: RSpec/MultipleDescribes:
Enabled: false Enabled: false
# Enforces the usage of the same method on all negative message expectations. # Checks if examples contain too many `expect` calls.
RSpec/MultipleExpectations:
Enabled: false
Max: 1
# Checks for explicitly referenced test subjects.
RSpec/NamedSubject:
Enabled: false
# Checks for nested example groups.
RSpec/NestedGroups:
Enabled: false
MaxNesting: 2
# Checks for consistent method usage for negating expectations.
RSpec/NotToNot: RSpec/NotToNot:
EnforcedStyle: not_to EnforcedStyle: not_to
Enabled: true Enabled: true
# Checks for stubbed test subjects.
RSpec/SubjectStub:
Enabled: false
# Prefer using verifying doubles over normal doubles. # Prefer using verifying doubles over normal doubles.
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:
Enabled: false Enabled: false
...@@ -3,10 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -3,10 +3,12 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.12.0 (unreleased) v 8.12.0 (unreleased)
- Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251
- Only check :can_resolve permission if the note is resolvable - Only check :can_resolve permission if the note is resolvable
- Bump fog-aws to v0.11.0 to support ap-south-1 region
- Add ability to fork to a specific namespace using API. (ritave) - Add ability to fork to a specific namespace using API. (ritave)
- Cleanup misalignments in Issue list view !6206 - Cleanup misalignments in Issue list view !6206
- Prune events older than 12 months. (ritave) - Prune events older than 12 months. (ritave)
- Prepend blank line to `Closes` message on merge request linked to issue (lukehowell) - Prepend blank line to `Closes` message on merge request linked to issue (lukehowell)
- Fix issues/merge-request templates dropdown for forked projects
- Filter tags by name !6121 - Filter tags by name !6121
- Update gitlab shell secret file also when it is empty. !3774 (glensc) - Update gitlab shell secret file also when it is empty. !3774 (glensc)
- Give project selection dropdowns responsive width, make non-wrapping. - Give project selection dropdowns responsive width, make non-wrapping.
...@@ -21,26 +23,32 @@ v 8.12.0 (unreleased) ...@@ -21,26 +23,32 @@ v 8.12.0 (unreleased)
- Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint
- Fix pagination on user snippets page - Fix pagination on user snippets page
- Fix sorting of issues in API - Fix sorting of issues in API
- Sort project variables by key. !6275 (Diego Souza)
- Ensure specs on sorting of issues in API are deterministic on MySQL - Ensure specs on sorting of issues in API are deterministic on MySQL
- Escape search term before passing it to Regexp.new !6241 (winniehell) - Escape search term before passing it to Regexp.new !6241 (winniehell)
- Fix pinned sidebar behavior in smaller viewports !6169 - Fix pinned sidebar behavior in smaller viewports !6169
- Fix file permissions change when updating a file on the Gitlab UI !5979 - Fix file permissions change when updating a file on the Gitlab UI !5979
- Change merge_error column from string to text type - Change merge_error column from string to text type
- Reduce contributions calendar data payload (ClemMakesApps) - Reduce contributions calendar data payload (ClemMakesApps)
- Replace contributions calendar timezone payload with dates (ClemMakesApps)
- Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel)
- Enable pipeline events by default !6278 - Enable pipeline events by default !6278
- Move parsing of sidekiq ps into helper !6245 (pascalbetz) - Move parsing of sidekiq ps into helper !6245 (pascalbetz)
- Added go to issue boards keyboard shortcut
- Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel) - Expose `sha` and `merge_commit_sha` in merge request API (Ben Boeckel)
- Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling) - Set path for all JavaScript cookies to honor GitLab's subdirectory setting !5627 (Mike Greiling)
- Fix blame table layout width - Fix blame table layout width
- Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps) - Fix bug where pagination is still displayed despite all todos marked as done (ClemMakesApps)
- Request only the LDAP attributes we need !6187 - Request only the LDAP attributes we need !6187
- Center build stage columns in pipeline overview (ClemMakesApps) - Center build stage columns in pipeline overview (ClemMakesApps)
- Fix bug with tooltip not hiding on discussion toggle button
- Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps) - Rename behaviour to behavior in bug issue template for consistency (ClemMakesApps)
- Fix bug stopping issue description being scrollable after selecting issue template
- Remove suggested colors hover underline (ClemMakesApps) - Remove suggested colors hover underline (ClemMakesApps)
- Shorten task status phrase (ClemMakesApps) - Shorten task status phrase (ClemMakesApps)
- Fix project visibility level fields on settings - Fix project visibility level fields on settings
- Add hover color to emoji icon (ClemMakesApps) - Add hover color to emoji icon (ClemMakesApps)
- Increase ci_builds artifacts_size column to 8-byte integer to allow larger files
- Add textarea autoresize after comment (ClemMakesApps) - Add textarea autoresize after comment (ClemMakesApps)
- Refresh todos count cache when an Issue/MR is deleted - Refresh todos count cache when an Issue/MR is deleted
- Fix branches page dropdown sort alignment (ClemMakesApps) - Fix branches page dropdown sort alignment (ClemMakesApps)
...@@ -60,10 +68,12 @@ v 8.12.0 (unreleased) ...@@ -60,10 +68,12 @@ v 8.12.0 (unreleased)
- Use 'git update-ref' for safer web commits !6130 - Use 'git update-ref' for safer web commits !6130
- Sort pipelines requested through the API - Sort pipelines requested through the API
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling) - Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Fix issue boards loading on large screens
- Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084 - Change pipeline duration to be jobs running time instead of simple wall time from start to end !6084
- Show queued time when showing a pipeline !6084 - Show queued time when showing a pipeline !6084
- Remove unused mixins (ClemMakesApps) - Remove unused mixins (ClemMakesApps)
- Add search to all issue board lists - Add search to all issue board lists
- Scroll active tab into view on mobile
- Fix groups sort dropdown alignment (ClemMakesApps) - Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps) - Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Use JavaScript tooltips for mentions !5301 (winniehell) - Use JavaScript tooltips for mentions !5301 (winniehell)
...@@ -74,7 +84,8 @@ v 8.12.0 (unreleased) ...@@ -74,7 +84,8 @@ v 8.12.0 (unreleased)
- Add last commit time to repo view (ClemMakesApps) - Add last commit time to repo view (ClemMakesApps)
- Fix accessibility and visibility of project list dropdown button !6140 - Fix accessibility and visibility of project list dropdown button !6140
- Fix missing flash messages on service edit page (airatshigapov) - Fix missing flash messages on service edit page (airatshigapov)
- Added project specific enable/disable setting for LFS !5997 - Added project-specific enable/disable setting for LFS !5997
- Added group-specific enable/disable setting for LFS !6164
- Don't expose a user's token in the `/api/v3/user` API (!6047) - Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps) - Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Ability to manage project issues, snippets, wiki, merge requests and builds access level - Ability to manage project issues, snippets, wiki, merge requests and builds access level
...@@ -137,6 +148,7 @@ v 8.12.0 (unreleased) ...@@ -137,6 +148,7 @@ v 8.12.0 (unreleased)
- Add specs to removing project (Katarzyna Kobierska Ula Budziszewska) - Add specs to removing project (Katarzyna Kobierska Ula Budziszewska)
- Clean environment variables when running git hooks - Clean environment variables when running git hooks
- Add UX improvements for merge request version diffs - Add UX improvements for merge request version diffs
- Fix non-master branch readme display in tree view
v 8.11.6 v 8.11.6
- Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005
...@@ -159,6 +171,7 @@ v 8.11.5 ...@@ -159,6 +171,7 @@ v 8.11.5
- Scope webhooks/services that will run for confidential issues - Scope webhooks/services that will run for confidential issues
- Remove gitorious from import_sources - Remove gitorious from import_sources
- Fix confidential issues being exposed as public using gitlab.com export - Fix confidential issues being exposed as public using gitlab.com export
- Use oj gem for faster JSON processing
v 8.11.4 v 8.11.4
- Fix resolving conflicts on forks. !6082 - Fix resolving conflicts on forks. !6082
......
...@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs]. ...@@ -91,19 +91,7 @@ This was inspired by [an article by Kent C. Dodds][medium-up-for-grabs].
## Implement design & UI elements ## Implement design & UI elements
### Design reference Please see the [UI Guide for building GitLab].
The GitLab design reference can be found in the [gitlab-design] project.
The designs are made using Antetype (`.atype` files). You can use the
[free Antetype viewer (Mac OSX only)] or grab an exported PNG from the design
(the PNG is 1:1).
The current designs can be found in the [`gitlab8.atype` file].
### UI development kit
Implemented UI elements can also be found at https://gitlab.com/help/ui. Please
note that this page isn't comprehensive at this time.
## Issue tracker ## Issue tracker
...@@ -289,6 +277,8 @@ request is as follows: ...@@ -289,6 +277,8 @@ request is as follows:
1. For more complex migrations, write tests. 1. For more complex migrations, write tests.
1. Merge requests **must** adhere to the [merge request performance 1. Merge requests **must** adhere to the [merge request performance
guidelines](doc/development/merge_request_performance_guidelines.md). guidelines](doc/development/merge_request_performance_guidelines.md).
1. For tests that use Capybara or PhantomJS, see this [article on how
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
The **official merge window** is in the beginning of the month from the 1st to The **official merge window** is in the beginning of the month from the 1st to
the 7th day of the month. This is the best time to submit an MR and get the 7th day of the month. This is the best time to submit an MR and get
...@@ -489,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -489,7 +479,5 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide" [doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
[scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide" [scss-styleguide]: doc/development/scss_styleguide.md "SCSS styleguide"
[newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide" [newlines-styleguide]: doc/development/newlines_styleguide.md "Newlines styleguide"
[gitlab-design]: https://gitlab.com/gitlab-org/gitlab-design [UI Guide for building GitLab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/ui_guide.md
[free Antetype viewer (Mac OSX only)]: https://itunes.apple.com/us/app/antetype-viewer/id824152298?mt=12
[`gitlab8.atype` file]: https://gitlab.com/gitlab-org/gitlab-design/tree/master/current/
[license-finder-doc]: doc/development/licensing.md [license-finder-doc]: doc/development/licensing.md
...@@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6' ...@@ -206,6 +206,9 @@ gem 'mousetrap-rails', '~> 1.4.6'
# Detect and convert string character encoding # Detect and convert string character encoding
gem 'charlock_holmes', '~> 0.7.3' gem 'charlock_holmes', '~> 0.7.3'
# Faster JSON
gem 'oj', '~> 2.17.4'
# Parse time & duration # Parse time & duration
gem 'chronic', '~> 0.10.2' gem 'chronic', '~> 0.10.2'
gem 'chronic_duration', '~> 0.10.6' gem 'chronic_duration', '~> 0.10.6'
...@@ -296,7 +299,7 @@ group :development, :test do ...@@ -296,7 +299,7 @@ group :development, :test do
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.42.0', require: false gem 'rubocop', '~> 0.42.0', require: false
gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'rubocop-rspec', '~> 1.7.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'haml_lint', '~> 0.18.2', require: false gem 'haml_lint', '~> 0.18.2', require: false
gem 'simplecov', '0.12.0', require: false gem 'simplecov', '0.12.0', require: false
......
...@@ -189,7 +189,7 @@ GEM ...@@ -189,7 +189,7 @@ GEM
erubis (2.7.0) erubis (2.7.0)
escape_utils (1.1.1) escape_utils (1.1.1)
eventmachine (1.0.8) eventmachine (1.0.8)
excon (0.49.0) excon (0.52.0)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.5.0) factory_girl (4.5.0)
...@@ -215,8 +215,8 @@ GEM ...@@ -215,8 +215,8 @@ GEM
flowdock (0.7.1) flowdock (0.7.1)
httparty (~> 0.7) httparty (~> 0.7)
multi_json multi_json
fog-aws (0.9.2) fog-aws (0.11.0)
fog-core (~> 1.27) fog-core (~> 1.38)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
ipaddress (~> 0.8) ipaddress (~> 0.8)
...@@ -225,7 +225,7 @@ GEM ...@@ -225,7 +225,7 @@ GEM
fog-core (~> 1.27) fog-core (~> 1.27)
fog-json (~> 1.0) fog-json (~> 1.0)
fog-xml (~> 0.1) fog-xml (~> 0.1)
fog-core (1.40.0) fog-core (1.42.0)
builder builder
excon (~> 0.49) excon (~> 0.49)
formatador (~> 0.2) formatador (~> 0.2)
...@@ -427,6 +427,7 @@ GEM ...@@ -427,6 +427,7 @@ GEM
rack (>= 1.2, < 3) rack (>= 1.2, < 3)
octokit (4.3.0) octokit (4.3.0)
sawyer (~> 0.7.0, >= 0.5.3) sawyer (~> 0.7.0, >= 0.5.3)
oj (2.17.4)
omniauth (1.3.1) omniauth (1.3.1)
hashie (>= 1.2, < 4) hashie (>= 1.2, < 4)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
...@@ -625,8 +626,8 @@ GEM ...@@ -625,8 +626,8 @@ GEM
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1) unicode-display_width (~> 1.0, >= 1.0.1)
rubocop-rspec (1.5.0) rubocop-rspec (1.7.0)
rubocop (>= 0.40.0) rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-prof (0.15.9) ruby-prof (0.15.9)
...@@ -904,6 +905,7 @@ DEPENDENCIES ...@@ -904,6 +905,7 @@ DEPENDENCIES
nokogiri (~> 1.6.7, >= 1.6.7.2) nokogiri (~> 1.6.7, >= 1.6.7.2)
oauth2 (~> 1.2.0) oauth2 (~> 1.2.0)
octokit (~> 4.3.0) octokit (~> 4.3.0)
oj (~> 2.17.4)
omniauth (~> 1.3.1) omniauth (~> 1.3.1)
omniauth-auth0 (~> 1.4.1) omniauth-auth0 (~> 1.4.1)
omniauth-azure-oauth2 (~> 0.0.6) omniauth-azure-oauth2 (~> 0.0.6)
...@@ -945,7 +947,7 @@ DEPENDENCIES ...@@ -945,7 +947,7 @@ DEPENDENCIES
rspec-rails (~> 3.5.0) rspec-rails (~> 3.5.0)
rspec-retry (~> 0.4.5) rspec-retry (~> 0.4.5)
rubocop (~> 0.42.0) rubocop (~> 0.42.0)
rubocop-rspec (~> 1.5.0) rubocop-rspec (~> 1.7.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
ruby-prof (~> 0.15.9) ruby-prof (~> 0.15.9)
sanitize (~> 2.0) sanitize (~> 2.0)
......
...@@ -251,6 +251,7 @@ ...@@ -251,6 +251,7 @@
} else { } else {
notesHolders.hide(); notesHolders.hide();
} }
$this.trigger('blur');
return e.preventDefault(); return e.preventDefault();
}); });
$document.off("click", '.js-confirm-danger'); $document.off("click", '.js-confirm-danger');
......
...@@ -13,6 +13,9 @@ ...@@ -13,6 +13,9 @@
this.buildDropdown(); this.buildDropdown();
this.bindEvents(); this.bindEvents();
this.onFilenameUpdate(); this.onFilenameUpdate();
this.autosizeUpdateEvent = document.createEvent('Event');
this.autosizeUpdateEvent.initEvent('autosize:update', true, false);
} }
TemplateSelector.prototype.buildDropdown = function() { TemplateSelector.prototype.buildDropdown = function() {
...@@ -72,6 +75,10 @@ ...@@ -72,6 +75,10 @@
TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) { TemplateSelector.prototype.requestFileSuccess = function(file, skipFocus) {
this.editor.setValue(file.content, 1); this.editor.setValue(file.content, 1);
if (!skipFocus) this.editor.focus(); if (!skipFocus) this.editor.focus();
if (this.editor instanceof jQuery) {
this.editor.get(0).dispatchEvent(this.autosizeUpdateEvent);
}
}; };
TemplateSelector.prototype.startLoadingSpinner = function() { TemplateSelector.prototype.startLoadingSpinner = function() {
......
...@@ -34,6 +34,11 @@ ...@@ -34,6 +34,11 @@
}, },
issues () { issues () {
this.$nextTick(() => { this.$nextTick(() => {
if (this.scrollHeight() <= this.listHeight() && this.list.issuesSize > this.list.issues.length) {
this.list.page++;
this.list.getIssues(false);
}
if (this.scrollHeight() > this.listHeight()) { if (this.scrollHeight() > this.listHeight()) {
this.showCount = true; this.showCount = true;
} else { } else {
......
...@@ -352,7 +352,13 @@ ...@@ -352,7 +352,13 @@
if (self.options.clicked) { if (self.options.clicked) {
self.options.clicked(selected, $el, e); self.options.clicked(selected, $el, e);
} }
return $el.trigger('blur');
// Update label right after all modifications in dropdown has been done
if (self.options.toggleLabel) {
self.updateLabel(selected, $el, self);
}
$el.trigger('blur');
}); });
} }
} }
...@@ -529,7 +535,7 @@ ...@@ -529,7 +535,7 @@
} else { } else {
if (!selected) { if (!selected) {
value = this.options.id ? this.options.id(data) : data.id; value = this.options.id ? this.options.id(data) : data.id;
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName() : this.options.fieldName; fieldName = this.options.fieldName;
field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']"); field = this.dropdown.parent().find("input[name='" + fieldName + "'][value='" + value + "']");
if (field.length) { if (field.length) {
...@@ -589,6 +595,7 @@ ...@@ -589,6 +595,7 @@
GitLabDropdown.prototype.rowClicked = function(el) { GitLabDropdown.prototype.rowClicked = function(el) {
var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value; var field, fieldName, groupName, isInput, selectedIndex, selectedObject, value;
fieldName = this.options.fieldName;
isInput = $(this.el).is('input'); isInput = $(this.el).is('input');
if (this.renderedData) { if (this.renderedData) {
groupName = el.data('group'); groupName = el.data('group');
...@@ -600,7 +607,6 @@ ...@@ -600,7 +607,6 @@
selectedObject = this.renderedData[selectedIndex]; selectedObject = this.renderedData[selectedIndex];
} }
} }
fieldName = typeof this.options.fieldName === 'function' ? this.options.fieldName(selectedObject) : this.options.fieldName;
value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id; value = this.options.id ? this.options.id(selectedObject, el) : selectedObject.id;
if (isInput) { if (isInput) {
field = $(this.el); field = $(this.el);
...@@ -644,11 +650,6 @@ ...@@ -644,11 +650,6 @@
} }
} }
// Update label right after input has been added
if (this.options.toggleLabel) {
this.updateLabel(selectedObject, el, this);
}
return selectedObject; return selectedObject;
}; };
...@@ -659,9 +660,6 @@ ...@@ -659,9 +660,6 @@
if (this.options.inputId != null) { if (this.options.inputId != null) {
$input.attr('id', this.options.inputId); $input.attr('id', this.options.inputId);
} }
if (selectedObject && selectedObject.type) {
$input.attr('data-type', selectedObject.type);
}
return this.dropdown.before($input); return this.dropdown.before($input);
}; };
......
...@@ -10,11 +10,13 @@ ...@@ -10,11 +10,13 @@
}; };
$(function() { $(function() {
hideEndFade($('.scrolling-tabs')); var $scrollingTabs = $('.scrolling-tabs');
hideEndFade($scrollingTabs);
$(window).off('resize.nav').on('resize.nav', function() { $(window).off('resize.nav').on('resize.nav', function() {
return hideEndFade($('.scrolling-tabs')); return hideEndFade($scrollingTabs);
}); });
return $('.scrolling-tabs').on('scroll', function(event) { $scrollingTabs.off('scroll').on('scroll', function(event) {
var $this, currentPosition, maxPosition; var $this, currentPosition, maxPosition;
$this = $(this); $this = $(this);
currentPosition = $this.scrollLeft(); currentPosition = $this.scrollLeft();
...@@ -22,6 +24,23 @@ ...@@ -22,6 +24,23 @@
$this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0); $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1); return $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
}); });
$scrollingTabs.each(function () {
var $this = $(this),
scrollingTabWidth = $this.width(),
$active = $this.find('.active'),
activeWidth = $active.width();
if ($active.length) {
var offset = $active.offset().left + activeWidth;
if (offset > scrollingTabWidth - 30) {
var scrollLeft = scrollingTabWidth / 2;
scrollLeft = (offset - scrollLeft) - (activeWidth / 2);
$this.scrollLeft(scrollLeft);
}
}
});
}); });
}).call(this); }).call(this);
...@@ -232,10 +232,10 @@ ...@@ -232,10 +232,10 @@
$('.hll').removeClass('hll'); $('.hll').removeClass('hll');
locationHash = window.location.hash; locationHash = window.location.hash;
if (locationHash !== '') { if (locationHash !== '') {
hashClassString = "." + (locationHash.replace('#', '')); dataLineString = '[data-line-code="' + locationHash.replace('#', '') + '"]';
$diffLine = $(locationHash + ":not(.match)", $('#diffs')); $diffLine = $(locationHash + ":not(.match)", $('#diffs'));
if (!$diffLine.is('tr')) { if (!$diffLine.is('tr')) {
$diffLine = $('#diffs').find("td" + locationHash + ", td" + hashClassString); $diffLine = $('#diffs').find("td" + locationHash + ", td" + dataLineString);
} else { } else {
$diffLine = $diffLine.find('td'); $diffLine = $diffLine.find('td');
} }
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
Mousetrap.bind('g i', function() { Mousetrap.bind('g i', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-issues');
}); });
Mousetrap.bind('g l', function() {
ShortcutsNavigation.findAndFollowLink('.shortcuts-issue-boards');
});
Mousetrap.bind('g m', function() { Mousetrap.bind('g m', function() {
return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests'); return ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests');
}); });
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
date.setDate(date.getDate() + i); date.setDate(date.getDate() + i);
var day = date.getDay(); var day = date.getDay();
var count = timestamps[date.getTime() * 0.001]; var count = timestamps[dateFormat(date, 'yyyy-mm-dd')];
// Create a new group array if this is the first day of the week // Create a new group array if this is the first day of the week
// or if is first object // or if is first object
......
...@@ -162,6 +162,10 @@ ul.content-list { ...@@ -162,6 +162,10 @@ ul.content-list {
margin-right: 0; margin-right: 0;
} }
} }
.no-comments {
opacity: 0.5;
}
} }
// When dragging a list item // When dragging a list item
......
...@@ -164,7 +164,7 @@ ...@@ -164,7 +164,7 @@
text-decoration: none; text-decoration: none;
&:after { &:after {
content: url('icon_anchor.svg'); content: image-url('icon_anchor.svg');
visibility: hidden; visibility: hidden;
} }
} }
......
lex
[v-cloak] { [v-cloak] {
display: none; display: none;
} }
...@@ -18,6 +19,10 @@ ...@@ -18,6 +19,10 @@
} }
} }
.is-ghost {
opacity: 0.3;
}
.dropdown-menu-issues-board-new { .dropdown-menu-issues-board-new {
width: 320px; width: 320px;
...@@ -34,47 +39,13 @@ ...@@ -34,47 +39,13 @@
> p { > p {
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;
color: #9c9c9c;
} }
} }
.issue-boards-page { .issue-boards-page {
.content-wrapper {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
}
.sub-nav,
.issues-filters {
-webkit-flex: none;
flex: none;
}
.page-with-sidebar { .page-with-sidebar {
display: -webkit-flex;
display: flex;
min-height: 100vh;
max-height: 100vh;
padding-bottom: 0; padding-bottom: 0;
} }
.issue-boards-content {
display: -webkit-flex;
display: flex;
-webkit-flex: 1;
flex: 1;
width: 100%;
.content {
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
width: 100%;
}
}
} }
.boards-app-loading { .boards-app-loading {
...@@ -83,46 +54,38 @@ ...@@ -83,46 +54,38 @@
} }
.boards-list { .boards-list {
display: -webkit-flex; height: calc(100vh - 152px);
display: flex; width: 100%;
-webkit-flex: 1;
flex: 1;
-webkit-flex-basis: 0;
flex-basis: 0;
min-height: calc(100vh - 152px);
max-height: calc(100vh - 152px);
padding-top: 25px; padding-top: 25px;
padding-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
overflow-x: scroll; overflow-x: scroll;
white-space: nowrap;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
height: 475px; // Needed for PhantomJS
height: calc(100vh - 220px);
min-height: 475px; min-height: 475px;
max-height: none;
} }
} }
.board { .board {
display: -webkit-flex; display: inline-block;
display: flex; width: calc(85vw - 15px);
min-width: calc(85vw - 15px); height: 100%;
max-width: calc(85vw - 15px);
margin-bottom: 25px;
padding-right: ($gl-padding / 2); padding-right: ($gl-padding / 2);
padding-left: ($gl-padding / 2); padding-left: ($gl-padding / 2);
white-space: normal;
vertical-align: top;
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
min-width: 400px; width: 400px;
max-width: 400px;
} }
} }
.board-inner { .board-inner {
display: -webkit-flex; height: 100%;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
width: 100%;
font-size: $issue-boards-font-size; font-size: $issue-boards-font-size;
background: $background-color; background: $background-color;
border: 1px solid $border-color; border: 1px solid $border-color;
...@@ -193,45 +156,31 @@ ...@@ -193,45 +156,31 @@
} }
.board-list { .board-list {
-webkit-flex: 1; height: calc(100% - 49px);
flex: 1;
height: 400px;
margin-bottom: 0; margin-bottom: 0;
padding: 5px; padding: 5px;
list-style: none;
overflow-y: scroll; overflow-y: scroll;
overflow-x: hidden; overflow-x: hidden;
} }
.board-list-loading { .board-list-loading {
margin-top: 10px; margin-top: 10px;
font-size: 26px; font-size: (26px / $issue-boards-font-size) * 1em;
}
.is-ghost {
opacity: 0.3;
} }
.card { .card {
position: relative; position: relative;
width: 100%;
padding: 10px $gl-padding; padding: 10px $gl-padding;
background: #fff; background: #fff;
border-radius: $border-radius-default; border-radius: $border-radius-default;
box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5); box-shadow: 0 1px 2px rgba(186, 186, 186, 0.5);
list-style: none; list-style: none;
&.user-can-drag {
padding-left: $gl-padding;
}
&:not(:last-child) { &:not(:last-child) {
margin-bottom: 5px; margin-bottom: 5px;
} }
a {
cursor: pointer;
}
.label { .label {
border: 0; border: 0;
outline: 0; outline: 0;
...@@ -256,14 +205,13 @@ ...@@ -256,14 +205,13 @@
line-height: 25px; line-height: 25px;
.label { .label {
margin-right: 4px; margin-right: 5px;
font-size: (14px / $issue-boards-font-size) * 1em; font-size: (14px / $issue-boards-font-size) * 1em;
} }
} }
.card-number { .card-number {
margin-right: 8px; margin-right: 5px;
font-weight: 500;
} }
.issue-boards-search { .issue-boards-search {
......
...@@ -10,10 +10,6 @@ ...@@ -10,10 +10,6 @@
.issue-labels { .issue-labels {
display: inline-block; display: inline-block;
} }
.issue-no-comments {
opacity: 0.5;
}
} }
} }
......
...@@ -231,10 +231,6 @@ ...@@ -231,10 +231,6 @@
.merge-request-labels { .merge-request-labels {
display: inline-block; display: inline-block;
} }
.merge-request-no-comments {
opacity: 0.5;
}
} }
.merge-request-angle { .merge-request-angle {
......
...@@ -147,14 +147,37 @@ ...@@ -147,14 +147,37 @@
} }
.stage-cell { .stage-cell {
text-align: center; font-size: 0;
svg { svg {
height: 18px; height: 18px;
width: 18px; width: 18px;
position: relative;
z-index: 2;
vertical-align: middle; vertical-align: middle;
overflow: visible; overflow: visible;
} }
.stage-container {
display: inline-block;
position: relative;
margin-right: 6px;
.tooltip {
white-space: nowrap;
}
&:not(:last-child) {
&::after {
content: '';
width: 8px;
position: absolute;;
right: -7px;
bottom: 8px;
border-bottom: 2px solid $border-color;
}
}
}
} }
.duration, .duration,
...@@ -318,9 +341,17 @@ ...@@ -318,9 +341,17 @@
.build-content { .build-content {
width: 130px; width: 130px;
.ci-status-text {
width: 110px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
vertical-align: middle;
display: inline-block;
position: relative;
top: -1px;
}
a { a {
color: $layout-link-gray; color: $layout-link-gray;
...@@ -331,13 +362,74 @@ ...@@ -331,13 +362,74 @@
text-decoration: underline; text-decoration: underline;
} }
} }
}
.dropdown-menu-toggle {
border: none;
width: auto;
padding: 0;
color: $layout-link-gray;
.ci-status-text {
width: 80px;
}
}
.grouped-pipeline-dropdown {
padding: 8px 0;
width: 200px;
left: auto;
right: -214px;
top: -9px;
a:hover {
.ci-status-text {
text-decoration: none;
}
}
.ci-status-text {
width: 145px;
}
.arrow {
&:before,
&:after {
content: '';
display: inline-block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
top: 18px;
}
&:before {
left: -5px;
margin-top: -6px;
border-width: 7px 5px 7px 0;
border-right-color: $border-color;
}
&:after {
left: -4px;
margin-top: -9px;
border-width: 10px 7px 10px 0;
border-right-color: $white-light;
}
}
}
.badge {
background-color: $gray-dark;
color: $layout-link-gray;
font-weight: normal;
} }
} }
svg { svg {
position: relative; vertical-align: middle;
top: 2px;
margin-right: 5px; margin-right: 5px;
} }
...@@ -442,7 +534,7 @@ ...@@ -442,7 +534,7 @@
width: 21px; width: 21px;
height: 25px; height: 25px;
position: absolute; position: absolute;
top: -28.5px; top: -29px;
border-top: 2px solid $border-color; border-top: 2px solid $border-color;
} }
......
...@@ -334,6 +334,10 @@ a.deploy-project-label { ...@@ -334,6 +334,10 @@ a.deploy-project-label {
a { a {
color: $gl-dark-link-color; color: $gl-dark-link-color;
} }
.dropdown-menu {
width: 240px;
}
} }
.last-push-widget { .last-push-widget {
......
...@@ -2,20 +2,6 @@ ...@@ -2,20 +2,6 @@
padding: 2px; padding: 2px;
} }
.snippet-holder {
margin-bottom: -$gl-padding;
.file-holder {
border-top: 0;
}
.file-actions {
.btn-clipboard {
@extend .btn;
}
}
}
.markdown-snippet-copy { .markdown-snippet-copy {
position: fixed; position: fixed;
top: -10px; top: -10px;
...@@ -24,29 +10,18 @@ ...@@ -24,29 +10,18 @@
max-width: 0; max-width: 0;
} }
.file-holder.snippet-file-content { .snippet-file-content {
padding-bottom: $gl-padding; border-radius: 3px;
border-bottom: 1px solid $border-color; .btn-clipboard {
@extend .btn;
.file-title {
padding-top: $gl-padding;
padding-bottom: $gl-padding;
}
.file-actions {
top: 12px;
}
.file-content {
border-left: 1px solid $border-color;
border-right: 1px solid $border-color;
border-bottom: 1px solid $border-color;
} }
} }
.snippet-title { .snippet-title {
font-size: 24px; font-size: 24px;
font-weight: normal; font-weight: 600;
padding: $gl-padding;
padding-left: 0;
} }
.snippet-actions { .snippet-actions {
......
...@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -60,6 +60,14 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level, :request_access_enabled) params.require(:group).permit(
:avatar,
:description,
:lfs_enabled,
:name,
:path,
:request_access_enabled,
:visibility_level
)
end end
end end
...@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController ...@@ -121,7 +121,17 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock, :request_access_enabled) params.require(:group).permit(
:avatar,
:description,
:lfs_enabled,
:name,
:path,
:public,
:request_access_enabled,
:share_with_group_lock,
:visibility_level
)
end end
def load_events def load_events
......
...@@ -74,7 +74,7 @@ class Projects::BuildsController < Projects::ApplicationController ...@@ -74,7 +74,7 @@ class Projects::BuildsController < Projects::ApplicationController
def erase def erase
@build.erase(erased_by: current_user) @build.erase(erased_by: current_user)
redirect_to namespace_project_build_path(project.namespace, project, @build), redirect_to namespace_project_build_path(project.namespace, project, @build),
notice: "Build has been sucessfully erased!" notice: "Build has been successfully erased!"
end end
def raw def raw
......
...@@ -73,7 +73,7 @@ class UsersController < ApplicationController ...@@ -73,7 +73,7 @@ class UsersController < ApplicationController
def calendar def calendar
calendar = contributions_calendar calendar = contributions_calendar
@timestamps = calendar.timestamps @activity_dates = calendar.activity_dates
render 'calendar', layout: false render 'calendar', layout: false
end end
......
...@@ -23,4 +23,29 @@ module GroupsHelper ...@@ -23,4 +23,29 @@ module GroupsHelper
full_title full_title
end end
end end
def projects_lfs_status(group)
lfs_status =
if group.lfs_enabled?
group.projects.select(&:lfs_enabled?).size
else
group.projects.reject(&:lfs_enabled?).size
end
size = group.projects.size
if lfs_status == size
'for all projects'
else
"for #{lfs_status} out of #{pluralize(size, 'project')}"
end
end
def group_lfs_status(group)
status = group.lfs_enabled? ? 'enabled' : 'disabled'
content_tag(:span, class: "lfs-#{status}") do
"#{status.humanize} #{projects_lfs_status(group)}"
end
end
end end
...@@ -27,7 +27,7 @@ module ProjectsHelper ...@@ -27,7 +27,7 @@ module ProjectsHelper
author_html = "" author_html = ""
# Build avatar image tag # Build avatar image tag
author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt: '') if opts[:avatar] author_html << image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]} #{opts[:avatar_class] if opts[:avatar_class]}", alt: '') if opts[:avatar]
# Build name span tag # Build name span tag
if opts[:by_username] if opts[:by_username]
......
module SnippetsHelper module SnippetsHelper
def reliable_snippet_path(snippet) def reliable_snippet_path(snippet, opts = nil)
if snippet.project_id? if snippet.project_id?
namespace_project_snippet_path(snippet.project.namespace, namespace_project_snippet_path(snippet.project.namespace,
snippet.project, snippet) snippet.project, snippet, opts)
else else
snippet_path(snippet) snippet_path(snippet, opts)
end end
end end
......
...@@ -11,6 +11,8 @@ module Ci ...@@ -11,6 +11,8 @@ module Ci
format: { with: /\A[a-zA-Z0-9_]+\z/, format: { with: /\A[a-zA-Z0-9_]+\z/,
message: "can contain only letters, digits and '_'." } message: "can contain only letters, digits and '_'." }
scope :order_key_asc, -> { reorder(key: :asc) }
attr_encrypted :value, attr_encrypted :value,
mode: :per_attribute_iv_and_salt, mode: :per_attribute_iv_and_salt,
insecure_mode: true, insecure_mode: true,
......
...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base ...@@ -69,17 +69,15 @@ class CommitStatus < ActiveRecord::Base
commit_status.update_attributes finished_at: Time.now commit_status.update_attributes finished_at: Time.now
end end
# We use around_transition to process pipeline on next stages as soon as possible, before the `after_*` is executed
around_transition any => [:success, :failed, :canceled] do |commit_status, block|
block.call
commit_status.pipeline.try(:process!)
end
after_transition do |commit_status, transition| after_transition do |commit_status, transition|
commit_status.pipeline.try(:build_updated) unless transition.loopback? commit_status.pipeline.try(:build_updated) unless transition.loopback?
end end
after_transition any => [:success, :failed, :canceled] do |commit_status|
commit_status.pipeline.try(:process!)
true
end
after_transition [:created, :pending, :running] => :success do |commit_status| after_transition [:created, :pending, :running] => :success do |commit_status|
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status)
end end
...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -95,6 +93,10 @@ class CommitStatus < ActiveRecord::Base
pipeline.before_sha || Gitlab::Git::BLANK_SHA pipeline.before_sha || Gitlab::Git::BLANK_SHA
end end
def group_name
name.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip
end
def self.stages def self.stages
# We group by stage name, but order stages by theirs' index # We group by stage name, but order stages by theirs' index
unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage') unscoped.from(all, :sg).group('stage').order('max(stage_idx)', 'stage').pluck('sg.stage')
...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base ...@@ -113,6 +115,10 @@ class CommitStatus < ActiveRecord::Base
allow_failure? && (failed? || canceled?) allow_failure? && (failed? || canceled?)
end end
def playable?
false
end
def duration def duration
calculate_duration calculate_duration
end end
......
...@@ -8,8 +8,9 @@ module HasStatus ...@@ -8,8 +8,9 @@ module HasStatus
class_methods do class_methods do
def status_sql def status_sql
scope = all.relevant scope = all
builds = scope.select('count(*)').to_sql builds = scope.select('count(*)').to_sql
created = scope.created.select('count(*)').to_sql
success = scope.success.select('count(*)').to_sql success = scope.success.select('count(*)').to_sql
ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored) ignored = scope.ignored.select('count(*)').to_sql if scope.respond_to?(:ignored)
ignored ||= '0' ignored ||= '0'
...@@ -19,12 +20,12 @@ module HasStatus ...@@ -19,12 +20,12 @@ module HasStatus
skipped = scope.skipped.select('count(*)').to_sql skipped = scope.skipped.select('count(*)').to_sql
deduce_status = "(CASE deduce_status = "(CASE
WHEN (#{builds})=0 THEN NULL WHEN (#{builds})=(#{created}) THEN NULL
WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success' WHEN (#{builds})=(#{success})+(#{ignored})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{pending})+(#{skipped}) THEN 'pending' WHEN (#{builds})=(#{created})+(#{pending})+(#{skipped}) THEN 'pending'
WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled' WHEN (#{builds})=(#{canceled})+(#{success})+(#{ignored})+(#{skipped}) THEN 'canceled'
WHEN (#{running})+(#{pending})>0 THEN 'running' WHEN (#{running})+(#{pending})+(#{created})>0 THEN 'running'
ELSE 'failed' ELSE 'failed'
END)" END)"
......
...@@ -95,6 +95,13 @@ class Group < Namespace ...@@ -95,6 +95,13 @@ class Group < Namespace
end end
end end
def lfs_enabled?
return false unless Gitlab.config.lfs.enabled
return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
self[:lfs_enabled]
end
def add_users(user_ids, access_level, current_user: nil, expires_at: nil) def add_users(user_ids, access_level, current_user: nil, expires_at: nil)
user_ids.each do |user_id| user_ids.each do |user_id|
Member.add_user( Member.add_user(
......
...@@ -652,7 +652,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -652,7 +652,7 @@ class MergeRequest < ActiveRecord::Base
end end
def environments def environments
return unless diff_head_commit return [] unless diff_head_commit
target_project.environments.select do |environment| target_project.environments.select do |environment|
environment.includes_commit?(diff_head_commit) environment.includes_commit?(diff_head_commit)
......
...@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base ...@@ -141,6 +141,11 @@ class Namespace < ActiveRecord::Base
projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id) projects.joins(:forked_project_link).find_by('forked_project_links.forked_from_project_id = ?', project.id)
end end
def lfs_enabled?
# User namespace will always default to the global setting
Gitlab.config.lfs.enabled
end
private private
def repository_storage_paths def repository_storage_paths
......
...@@ -393,10 +393,9 @@ class Project < ActiveRecord::Base ...@@ -393,10 +393,9 @@ class Project < ActiveRecord::Base
end end
def lfs_enabled? def lfs_enabled?
return false unless Gitlab.config.lfs.enabled return namespace.lfs_enabled? if self[:lfs_enabled].nil?
return Gitlab.config.lfs.enabled if self[:lfs_enabled].nil?
self[:lfs_enabled] self[:lfs_enabled] && Gitlab.config.lfs.enabled
end end
def repository_storage_path def repository_storage_path
......
...@@ -16,11 +16,29 @@ module Commits ...@@ -16,11 +16,29 @@ module Commits
error(ex.message) error(ex.message)
end end
private
def commit def commit
raise NotImplementedError raise NotImplementedError
end end
private def commit_change(action)
raise NotImplementedError unless repository.respond_to?(action)
into = @create_merge_request ? @commit.public_send("#{action}_branch_name") : @target_branch
tree_id = repository.public_send("check_#{action}_content", @commit, @target_branch)
if tree_id
create_target_branch(into) if @create_merge_request
repository.public_send(action, current_user, @commit, into, tree_id)
success
else
error_msg = "Sorry, we cannot #{action.to_s.dasherize} this #{@commit.change_type_title} automatically.
It may have already been #{action.to_s.dasherize}, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end
def check_push_permissions def check_push_permissions
allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch) allowed = ::Gitlab::UserAccess.new(current_user, project: project).can_push_to_branch?(@target_branch)
......
module Commits module Commits
class CherryPickService < ChangeService class CherryPickService < ChangeService
def commit def commit
cherry_pick_into = @create_merge_request ? @commit.cherry_pick_branch_name : @target_branch commit_change(:cherry_pick)
cherry_pick_tree_id = repository.check_cherry_pick_content(@commit, @target_branch)
if cherry_pick_tree_id
create_target_branch(cherry_pick_into) if @create_merge_request
repository.cherry_pick(current_user, @commit, cherry_pick_into, cherry_pick_tree_id)
success
else
error_msg = "Sorry, we cannot cherry-pick this #{@commit.change_type_title} automatically.
It may have already been cherry-picked, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end end
end end
end end
module Commits module Commits
class RevertService < ChangeService class RevertService < ChangeService
def commit def commit
revert_into = @create_merge_request ? @commit.revert_branch_name : @target_branch commit_change(:revert)
revert_tree_id = repository.check_revert_content(@commit, @target_branch)
if revert_tree_id
create_target_branch(revert_into) if @create_merge_request
repository.revert(current_user, @commit, revert_into, revert_tree_id)
success
else
error_msg = "Sorry, we cannot revert this #{@commit.change_type_title} automatically.
It may have already been reverted, or a more recent commit may have updated some of its content."
raise ChangeError, error_msg
end
end end
end end
end end
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f = render 'shared/allow_request_access', form: f
= render 'groups/group_lfs_settings', f: f
- if @group.new_record? - if @group.new_record?
.form-group .form-group
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
......
...@@ -37,6 +37,12 @@ ...@@ -37,6 +37,12 @@
%strong %strong
= @group.created_at.to_s(:medium) = @group.created_at.to_s(:medium)
%li
%span.light Group Git LFS status:
%strong
= group_lfs_status(@group)
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.panel.panel-default .panel.panel-default
.panel-heading .panel-heading
%h3.panel-title %h3.panel-title
......
- if current_user.admin?
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :lfs_enabled do
= f.check_box :lfs_enabled, checked: @group.lfs_enabled?
%strong
Allow projects within this group to use Git LFS
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
%br/
%span.descr This setting can be overridden in each project.
\ No newline at end of file
...@@ -25,6 +25,8 @@ ...@@ -25,6 +25,8 @@
.col-sm-offset-2.col-sm-10 .col-sm-offset-2.col-sm-10
= render 'shared/allow_request_access', form: f = render 'shared/allow_request_access', form: f
= render 'group_lfs_settings', f: f
.form-group .form-group
%hr %hr
= f.label :share_with_group_lock, class: 'control-label' do = f.label :share_with_group_lock, class: 'control-label' do
......
...@@ -162,6 +162,12 @@ ...@@ -162,6 +162,12 @@
.key i .key i
%td %td
Go to issues Go to issues
%tr
%td.shortcut
.key g
.key l
%td
Go to issue boards
%tr %tr
%td.shortcut %td.shortcut
.key g .key g
......
...@@ -113,3 +113,7 @@ ...@@ -113,3 +113,7 @@
%li.hidden %li.hidden
= link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do = link_to project_commits_path(@project), title: 'Commits', class: 'shortcuts-commits' do
Commits Commits
-# Shortcut to issue boards
%li.hidden
= link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards'
- if @user.valid? - if @user.valid?
:plain :plain
new Flash("Username sucessfully changed", "notice") new Flash("Username successfully changed", "notice")
- else - else
:plain :plain
new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert") new Flash("Username change failed - #{@user.errors.full_messages.first}", "alert")
- is_playable = subject.playable? && can?(current_user, :update_build, @project) - is_playable = subject.playable? && can?(current_user, :update_build, @project)
%li.build{class: ("playable" if is_playable)} - if is_playable
.curve
.build-content
- if is_playable
= link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do = link_to play_namespace_project_build_path(subject.project.namespace, subject.project, subject, return_to: request.original_url), method: :post, title: 'Play' do
= render_status_with_link('build', 'play') = render_status_with_link('build', 'play')
%span.ci-status-text= subject.name .ci-status-text= subject.name
- elsif can?(current_user, :read_build, @project) - elsif can?(current_user, :read_build, @project)
= link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
%span.ci-status-text= subject.name .ci-status-text= subject.name
- else - else
= render_status_with_link('build', subject.status) = render_status_with_link('build', subject.status)
= ci_icon_for_status(subject.status) = ci_icon_for_status(subject.status)
...@@ -36,16 +36,14 @@ ...@@ -36,16 +36,14 @@
- stages_status = pipeline.statuses.relevant.latest.stages_status - stages_status = pipeline.statuses.relevant.latest.stages_status
- stages.each do |stage|
%td.stage-cell %td.stage-cell
- stages.each do |stage|
- status = stages_status[stage] - status = stages_status[stage]
- tooltip = "#{stage.titleize}: #{status || 'not found'}" - tooltip = "#{stage.titleize}: #{status || 'not found'}"
- if status - if status
.stage-container
= link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do = link_to namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id, anchor: stage), class: "has-tooltip ci-status-icon-#{status}", title: tooltip do
= ci_icon_for_status(status) = ci_icon_for_status(status)
- else
.light.has-tooltip{ title: tooltip }
\-
%td %td
- if pipeline.duration - if pipeline.duration
......
...@@ -39,8 +39,7 @@ ...@@ -39,8 +39,7 @@
= stage.titleize = stage.titleize
.builds-container .builds-container
%ul %ul
- statuses.each do |status| = render "projects/commit/pipeline_stage", statuses: statuses
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- if pipeline.yaml_errors.present? - if pipeline.yaml_errors.present?
......
- status_groups = statuses.group_by(&:group_name)
- status_groups.each do |group_name, grouped_statuses|
- if grouped_statuses.one?
- status = grouped_statuses.first
- is_playable = status.playable? && can?(current_user, :update_build, @project)
%li.build{ class: ("playable" if is_playable) }
.curve
.build-content
= render "projects/#{status.to_partial_path}_pipeline", subject: status
- else
%li.build
.curve
.build-content
= render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses
- group_status = CommitStatus.where(id: subject).status
= render_status_with_link('build', group_status)
.dropdown.inline
%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } }
%span.ci-status-text
= name
%span.badge= subject.size
%ul.dropdown-menu.grouped-pipeline-dropdown
.arrow
- subject.each do |status|
= render "projects/#{status.to_partial_path}_pipeline", subject: status
...@@ -84,15 +84,14 @@ ...@@ -84,15 +84,14 @@
= project_feature_access_select(:snippets_access_level) = project_feature_access_select(:snippets_access_level)
- if Gitlab.config.lfs.enabled && current_user.admin? - if Gitlab.config.lfs.enabled && current_user.admin?
.form-group .row
.checkbox .col-md-9
= f.label :lfs_enabled do = f.label :lfs_enabled, 'LFS', class: 'label-light'
= f.check_box :lfs_enabled, checked: @project.lfs_enabled? %span.help-block
%strong LFS
%br
%span.descr
Git Large File Storage Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control'
- if Gitlab.config.registry.enabled - if Gitlab.config.registry.enabled
.form-group .form-group
......
%li.build - if subject.target_url
.curve = link_to subject.target_url do
.build-content
- if subject.target_url
- link_to subject.target_url do
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
- else - else
= render_status_with_link('commit status', subject.status) = render_status_with_link('commit status', subject.status)
%span.ci-status-text= subject.name %span.ci-status-text= subject.name
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
- note_count = issue.notes.user.count - note_count = issue.notes.user.count
%li %li
= link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do = link_to issue_path(issue, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments') = icon('comments')
= note_count = note_count
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
- note_count = merge_request.mr_and_commit_notes.user.count - note_count = merge_request.mr_and_commit_notes.user.count
%li %li
= link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments') = icon('comments')
= note_count = note_count
......
...@@ -47,13 +47,7 @@ ...@@ -47,13 +47,7 @@
%tbody %tbody
%th Status %th Status
%th Commit %th Commit
- stages.each do |stage| %th Stages
%th.stage
- if stage.titleize.length > 12
%span.has-tooltip{ title: "#{stage.titleize}" }
= stage.titleize
- else
= stage.titleize
%th %th
%th %th
= render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages = render @pipelines, commit_sha: true, stage: true, allow_retry: true, stages: stages
......
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- if can?(current_user, :create_project_snippet, @project) - if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-create new-snippet-link', title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-warning", title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_project_snippet, @snippet)
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -19,11 +19,11 @@ ...@@ -19,11 +19,11 @@
%li %li
= link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- if can?(current_user, :update_project_snippet, @snippet) - if can?(current_user, :update_project_snippet, @snippet)
%li %li
= link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_project_snippet, @snippet)
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
- page_title @snippet.title, "Snippets" - page_title @snippet.title, "Snippets"
.snippet-holder = render 'shared/snippets/header'
= render 'shared/snippets/header'
%article.file-holder.file-holder-no-border.snippet-file-content %article.file-holder.snippet-file-content
.file-title.file-title-clear .file-title
= blob_icon 0, @snippet.file_name = blob_icon 0, @snippet.file_name
= @snippet.file_name = @snippet.file_name
.file-actions.hidden-xs .file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" = link_to 'Raw', raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob' = render 'shared/snippets/blob'
%div#notes= render "projects/notes/notes_with_form" %div#notes= render "projects/notes/notes_with_form"
%article.file-holder.readme-holder %article.file-holder.readme-holder
.file-title .file-title
= blob_icon readme.mode, readme.name = blob_icon readme.mode, readme.name
= link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, @path, readme.name)) do = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do
%strong %strong
= readme.name = readme.name
.file-content.wiki .file-content.wiki
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
%th Value %th Value
%th %th
%tbody %tbody
- @project.variables.each do |variable| - @project.variables.order_key_asc.each do |variable|
- if variable.id? - if variable.id?
%tr %tr
%td= variable.key %td= variable.key
......
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
in group #{link_to @group.name, @group} in group #{link_to @group.name, @group}
.results.prepend-top-10 .results.prepend-top-10
- if @scope == 'commits'
%ul.list-unstyled
= render partial: "search/results/commit", collection: @search_objects
- else
.search-results .search-results
- if @scope == 'projects' - if @scope == 'projects'
.term .term
......
.search-result-row = render 'projects/commits/commit', project: @project, commit: commit
= render 'projects/commits/commit', project: @project, commit: commit
.form-group.project-visibility-level-holder .form-group.project-visibility-level-holder
= f.label :visibility_level, class: 'control-label' do = f.label :visibility_level, class: 'control-label' do
Visibility Level Visibility Level
= link_to "(?)", help_page_path("public_access/public_access") = link_to icon('question-circle'), help_page_path("public_access/public_access")
.col-sm-10 .col-sm-10
- if can_change_visibility_level - if can_change_visibility_level
= render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model) = render('shared/visibility_radios', model_method: :visibility_level, form: f, selected_level: visibility_level, form_model: form_model)
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= dropdown_tag(title, options: { toggle_class: 'js-issuable-selector', = dropdown_tag(title, options: { toggle_class: 'js-issuable-selector',
title: title, filter: true, placeholder: 'Filter', footer_content: true, title: title, filter: true, placeholder: 'Filter', footer_content: true,
data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: @project.path, namespace_path: @project.namespace.path } } ) do data: { data: issuable_template_names, field_name: 'issuable_template', selected: selected_template(issuable), project_path: ref_project.path, namespace_path: ref_project.namespace.path } } ) do
%ul.dropdown-footer-list %ul.dropdown-footer-list
%li %li
%a.reset-template %a.reset-template
......
...@@ -6,12 +6,13 @@ ...@@ -6,12 +6,13 @@
%strong.item-title %strong.item-title
Snippet #{@snippet.to_reference} Snippet #{@snippet.to_reference}
%span.creator %span.creator
created by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title")} authored
= time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago') = time_ago_with_tooltip(@snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
- if @snippet.updated_at != @snippet.created_at - if @snippet.updated_at != @snippet.created_at
%span %span
= icon('edit', title: 'edited') = icon('edit', title: 'edited')
= time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago') = time_ago_with_tooltip(@snippet.updated_at, placement: 'bottom', html_class: 'snippet_edited_ago')
by #{link_to_member(@project, @snippet.author, size: 24, author_class: "author item-title", avatar_class: "hidden-xs")}
.snippet-actions .snippet-actions
- if @snippet.project_id? - if @snippet.project_id?
...@@ -19,6 +20,5 @@ ...@@ -19,6 +20,5 @@
- else - else
= render "snippets/actions" = render "snippets/actions"
.content-block.second-block %h2.snippet-title.prepend-top-0.append-bottom-0
%h2.snippet-title.prepend-top-0.append-bottom-0
= markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author = markdown escape_once(@snippet.title), pipeline: :single_line, author: @snippet.author
...@@ -3,19 +3,30 @@ ...@@ -3,19 +3,30 @@
.title .title
= link_to reliable_snippet_path(snippet) do = link_to reliable_snippet_path(snippet) do
= truncate(snippet.title, length: 60) = snippet.title
- if snippet.private? - if snippet.private?
%span.label.label-gray %span.label.label-gray.hidden-xs
= icon('lock') = icon('lock')
private private
%span.monospace.pull-right %span.monospace.pull-right.hidden-xs
= snippet.file_name = snippet.file_name
%small.pull-right.cgray %ul.controls.visible-xs
%li
- note_count = snippet.notes.user.count
= link_to reliable_snippet_path(snippet, anchor: 'notes'), class: ('no-comments' if note_count.zero?) do
= icon('comments')
= note_count
%li
%span.sr-only
= visibility_level_label(snippet.visibility_level)
= visibility_level_icon(snippet.visibility_level, fw: false)
%small.pull-right.cgray.hidden-xs
- if snippet.project_id? - if snippet.project_id?
= link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project)
.snippet-info .snippet-info.hidden-xs
= link_to user_snippets_path(snippet.author) do = link_to user_snippets_path(snippet.author) do
= snippet.author_name = snippet.author_name
authored #{time_ago_with_tooltip(snippet.created_at)} authored #{time_ago_with_tooltip(snippet.created_at)}
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
- if current_user - if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do = link_to new_snippet_path, class: "btn btn-grouped btn-create new-snippet-link", title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-grouped btn-danger", title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_personal_snippet, @snippet)
= link_to edit_snippet_path(@snippet), class: "btn btn-grouped snippable-edit" do
Edit
- if current_user - if current_user
.visible-xs-block.dropdown .visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } } %button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
...@@ -18,11 +18,11 @@ ...@@ -18,11 +18,11 @@
%li %li
= link_to new_snippet_path, title: "New Snippet" do = link_to new_snippet_path, title: "New Snippet" do
New Snippet New Snippet
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- if can?(current_user, :admin_personal_snippet, @snippet) - if can?(current_user, :admin_personal_snippet, @snippet)
%li %li
= link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do = link_to snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, title: 'Delete Snippet' do
Delete Delete
- if can?(current_user, :update_personal_snippet, @snippet)
%li
= link_to edit_snippet_path(@snippet) do
Edit
- page_title @snippet.title, "Snippets" - page_title @snippet.title, "Snippets"
.snippet-holder = render 'shared/snippets/header'
= render 'shared/snippets/header'
%article.file-holder.file-holder-no-border.snippet-file-content %article.file-holder.snippet-file-content
.file-title.file-title-clear .file-title
= blob_icon 0, @snippet.file_name = blob_icon 0, @snippet.file_name
= @snippet.file_name = @snippet.file_name
.file-actions.hidden-xs .file-actions
= clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']")
= link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank"
= render 'shared/snippets/blob' = render 'shared/snippets/blob'
...@@ -4,6 +4,6 @@ ...@@ -4,6 +4,6 @@
Summary of issues, merge requests, and push events Summary of issues, merge requests, and push events
:javascript :javascript
new Calendar( new Calendar(
#{@timestamps.to_json}, #{@activity_dates.to_json},
'#{user_calendar_activities_path}' '#{user_calendar_activities_path}'
); );
\ No newline at end of file
...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines ...@@ -3,9 +3,13 @@ class Gitlab::Seeder::Pipelines
BUILDS = [ BUILDS = [
{ name: 'build:linux', stage: 'build', status: :success }, { name: 'build:linux', stage: 'build', status: :success },
{ name: 'build:osx', stage: 'build', status: :success }, { name: 'build:osx', stage: 'build', status: :success },
{ name: 'rspec:linux', stage: 'test', status: :success }, { name: 'rspec:linux 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows', stage: 'test', status: :success }, { name: 'rspec:linux 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 0 3', stage: 'test', status: :success },
{ name: 'rspec:windows 1 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:windows 2 3', stage: 'test', status: :success },
{ name: 'rspec:osx', stage: 'test', status_event: :success }, { name: 'rspec:osx', stage: 'test', status_event: :success },
{ name: 'spinach:linux', stage: 'test', status: :success }, { name: 'spinach:linux', stage: 'test', status: :success },
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true},
......
# rubocop:disable all # rubocop:disable all
class MigrateRepoSize < ActiveRecord::Migration class MigrateRepoSize < ActiveRecord::Migration
DOWNTIME = false
def up def up
project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id') project_data = execute('SELECT projects.id, namespaces.path AS namespace_path, projects.path AS project_path FROM projects LEFT JOIN namespaces ON projects.namespace_id = namespaces.id')
project_data.each do |project| project_data.each do |project|
id = project['id'] id = project['id']
namespace_path = project['namespace_path'] || '' namespace_path = project['namespace_path'] || ''
path = File.join(Gitlab.config.gitlab_shell.repos_path, namespace_path, project['project_path'] + '.git') repos_path = Gitlab.config.gitlab_shell['repos_path'] || Gitlab.config.repositories.storages.default
path = File.join(repos_path, namespace_path, project['project_path'] + '.git')
begin begin
repo = Gitlab::Git::Repository.new(path) repo = Gitlab::Git::Repository.new(path)
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddLfsEnabledToNamespaces < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :namespaces, :lfs_enabled, :boolean
end
end
...@@ -14,6 +14,6 @@ class RemoveProjectsPushesSinceGc < ActiveRecord::Migration ...@@ -14,6 +14,6 @@ class RemoveProjectsPushesSinceGc < ActiveRecord::Migration
end end
def down def down
add_column_with_default! :projects, :pushes_since_gc, :integer, default: 0 add_column_with_default :projects, :pushes_since_gc, :integer, default: 0
end end
end end
class ChangeArtifactsSizeColumn < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = true
DOWNTIME_REASON = 'Changing an integer column size requires a full table rewrite.'
def up
change_column :ci_builds, :artifacts_size, :integer, limit: 8
end
def down
# do nothing
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160913162434) do ActiveRecord::Schema.define(version: 20160913212128) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -177,7 +177,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do ...@@ -177,7 +177,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do
t.datetime "erased_at" t.datetime "erased_at"
t.datetime "artifacts_expire_at" t.datetime "artifacts_expire_at"
t.string "environment" t.string "environment"
t.integer "artifacts_size" t.integer "artifacts_size", limit: 8
t.string "when" t.string "when"
t.text "yaml_variables" t.text "yaml_variables"
t.datetime "queued_at" t.datetime "queued_at"
...@@ -650,6 +650,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do ...@@ -650,6 +650,7 @@ ActiveRecord::Schema.define(version: 20160913162434) do
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: true, null: false t.boolean "request_access_enabled", default: true, null: false
t.datetime "deleted_at" t.datetime "deleted_at"
t.boolean "lfs_enabled"
end end
add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree
......
...@@ -288,6 +288,7 @@ Parameters: ...@@ -288,6 +288,7 @@ Parameters:
- `path` (required) - The path of the group - `path` (required) - The path of the group
- `description` (optional) - The group's description - `description` (optional) - The group's description
- `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public. - `visibility_level` (optional) - The group's visibility. 0 for private, 10 for internal, 20 for public.
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group
## Transfer project to group ## Transfer project to group
...@@ -317,6 +318,7 @@ PUT /groups/:id ...@@ -317,6 +318,7 @@ PUT /groups/:id
| `path` | string | no | The path of the group | | `path` | string | no | The path of the group |
| `description` | string | no | The description of the group | | `description` | string | no | The description of the group |
| `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. | | `visibility_level` | integer | no | The visibility level of the group. 0 for private, 10 for internal, 20 for public. |
| `lfs_enabled` (optional) | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
```bash ```bash
curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental" curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/groups/5?name=Experimental"
......
...@@ -78,7 +78,8 @@ Parameters: ...@@ -78,7 +78,8 @@ Parameters:
### Create new issue note ### Create new issue note
Creates a new note to a single project issue. Creates a new note to a single project issue. If you create a note where the body
only contains an Award Emoji, you'll receive this object back.
``` ```
POST /projects/:id/issues/:issue_id/notes POST /projects/:id/issues/:issue_id/notes
...@@ -204,6 +205,7 @@ Parameters: ...@@ -204,6 +205,7 @@ Parameters:
### Create new snippet note ### Create new snippet note
Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet. Creates a new note for a single snippet. Snippet notes are comments users can post to a snippet.
If you create a note where the body only contains an Award Emoji, you'll receive this object back.
``` ```
POST /projects/:id/snippets/:snippet_id/notes POST /projects/:id/snippets/:snippet_id/notes
...@@ -332,6 +334,8 @@ Parameters: ...@@ -332,6 +334,8 @@ Parameters:
### Create new merge request note ### Create new merge request note
Creates a new note for a single merge request. Creates a new note for a single merge request.
If you create a note where the body only contains an Award Emoji, you'll receive
this object back.
``` ```
POST /projects/:id/merge_requests/:merge_request_id/notes POST /projects/:id/merge_requests/:merge_request_id/notes
......
...@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other. ...@@ -105,7 +105,8 @@ What is important is that each job is run independently from each other.
If you want to check whether your `.gitlab-ci.yml` file is valid, there is a If you want to check whether your `.gitlab-ci.yml` file is valid, there is a
Lint tool under the page `/ci/lint` of your GitLab instance. You can also find Lint tool under the page `/ci/lint` of your GitLab instance. You can also find
the link under **Settings > CI settings** in your project. a "CI Lint" button to go to this page under **Pipelines > Pipelines** and
**Pipelines > Builds** in your project.
For more information and a complete `.gitlab-ci.yml` syntax, please read For more information and a complete `.gitlab-ci.yml` syntax, please read
[the documentation on .gitlab-ci.yml](../yaml/README.md). [the documentation on .gitlab-ci.yml](../yaml/README.md).
......
...@@ -111,6 +111,28 @@ class MyMigration < ActiveRecord::Migration ...@@ -111,6 +111,28 @@ class MyMigration < ActiveRecord::Migration
end end
``` ```
## Integer column type
By default, an integer column can hold up to a 4-byte (32-bit) number. That is
a max value of 2,147,483,647. Be aware of this when creating a column that will
hold file sizes in byte units. If you are tracking file size in bytes this
restricts the maximum file size to just over 2GB.
To allow an integer column to hold up to an 8-byte (64-bit) number, explicitly
set the limit to 8-bytes. This will allow the column to hold a value up to
9,223,372,036,854,775,807.
Rails migration example:
```
add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
# or
add_column(:projects, :foo, :integer, default: 10, limit: 8)
```
## Testing ## Testing
Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct. Make sure that your migration works with MySQL and PostgreSQL with data. An empty database does not guarantee that your migration is correct.
......
...@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project. ...@@ -63,7 +63,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^2] | | | | | | | Force push to protected branches [^2] | | | | | |
| Remove protected branches [^2] | | | | | | | Remove protected branches [^2] | | | | | |
[^1]: If **Allow guest to access builds** is enabled in CI settings [^1]: If **Public pipelines** is enabled in **Project Settings > CI/CD Pipelines**
[^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner [^2]: Not allowed for Guest, Reporter, Developer, Master, or Owner
## Group ## Group
......
...@@ -86,7 +86,8 @@ module API ...@@ -86,7 +86,8 @@ module API
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) } expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) }
expose :created_at, :last_activity_at expose :created_at, :last_activity_at
expose :shared_runners_enabled, :lfs_enabled expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id expose :creator_id
expose :namespace expose :namespace
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
...@@ -121,6 +122,7 @@ module API ...@@ -121,6 +122,7 @@ module API
class Group < Grape::Entity class Group < Grape::Entity
expose :id, :name, :path, :description, :visibility_level expose :id, :name, :path, :description, :visibility_level
expose :lfs_enabled?, as: :lfs_enabled
expose :avatar_url expose :avatar_url
expose :web_url expose :web_url
end end
......
...@@ -27,13 +27,14 @@ module API ...@@ -27,13 +27,14 @@ module API
# path (required) - The path of the group # path (required) - The path of the group
# description (optional) - The description of the group # description (optional) - The description of the group
# visibility_level (optional) - The visibility level of the group # visibility_level (optional) - The visibility level of the group
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group
# Example Request: # Example Request:
# POST /groups # POST /groups
post do post do
authorize! :create_group authorize! :create_group
required_attributes! [:name, :path] required_attributes! [:name, :path]
attrs = attributes_for_keys [:name, :path, :description, :visibility_level] attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled]
@group = Group.new(attrs) @group = Group.new(attrs)
if @group.save if @group.save
...@@ -51,13 +52,14 @@ module API ...@@ -51,13 +52,14 @@ module API
# path (optional) - The path of the group # path (optional) - The path of the group
# description (optional) - The description of the group # description (optional) - The description of the group
# visibility_level (optional) - The visibility level of the group # visibility_level (optional) - The visibility level of the group
# lfs_enabled (optional) - Enable/disable LFS for the projects in this group
# Example Request: # Example Request:
# PUT /groups/:id # PUT /groups/:id
put ':id' do put ':id' do
group = find_group(params[:id]) group = find_group(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
attrs = attributes_for_keys [:name, :path, :description, :visibility_level] attrs = attributes_for_keys [:name, :path, :description, :visibility_level, :lfs_enabled]
if ::Groups::UpdateService.new(group, current_user, attrs).execute if ::Groups::UpdateService.new(group, current_user, attrs).execute
present group, with: Entities::GroupDetail present group, with: Entities::GroupDetail
......
...@@ -83,12 +83,12 @@ module API ...@@ -83,12 +83,12 @@ module API
opts[:created_at] = params[:created_at] opts[:created_at] = params[:created_at]
end end
@note = ::Notes::CreateService.new(user_project, current_user, opts).execute note = ::Notes::CreateService.new(user_project, current_user, opts).execute
if @note.valid? if note.valid?
present @note, with: Entities::Note present note, with: Entities::const_get(note.class.name)
else else
not_found!("Note #{@note.errors.messages}") not_found!("Note #{note.errors.messages}")
end end
end end
......
module Gitlab module Gitlab
class ContributionsCalendar class ContributionsCalendar
attr_reader :timestamps, :projects, :user attr_reader :activity_dates, :projects, :user
def initialize(projects, user) def initialize(projects, user)
@projects = projects @projects = projects
@user = user @user = user
end end
def timestamps def activity_dates
return @timestamps if @timestamps.present? return @activity_dates if @activity_dates.present?
@timestamps = {} @activity_dates = {}
date_from = 1.year.ago date_from = 1.year.ago
events = Event.reorder(nil).contributions.where(author_id: user.id). events = Event.reorder(nil).contributions.where(author_id: user.id).
...@@ -19,18 +19,17 @@ module Gitlab ...@@ -19,18 +19,17 @@ module Gitlab
select('date(created_at) as date, count(id) as total_amount'). select('date(created_at) as date, count(id) as total_amount').
map(&:attributes) map(&:attributes)
dates = (1.year.ago.to_date..Date.today).to_a activity_dates = (1.year.ago.to_date..Date.today).to_a
dates.each do |date| activity_dates.each do |date|
date_id = date.to_time.to_i.to_s
day_events = events.find { |day_events| day_events["date"] == date } day_events = events.find { |day_events| day_events["date"] == date }
if day_events if day_events
@timestamps[date_id] = day_events["total_amount"] @activity_dates[date] = day_events["total_amount"]
end end
end end
@timestamps @activity_dates
end end
def events_by_date(date) def events_by_date(date)
......
...@@ -129,12 +129,14 @@ module Gitlab ...@@ -129,12 +129,14 @@ module Gitlab
# column - The name of the column to add. # column - The name of the column to add.
# type - The column type (e.g. `:integer`). # type - The column type (e.g. `:integer`).
# default - The default value for the column. # default - The default value for the column.
# limit - Sets a column limit. For example, for :integer, the default is
# 4-bytes. Set `limit: 8` to allow 8-byte integers.
# allow_null - When set to `true` the column will allow NULL values, the # allow_null - When set to `true` the column will allow NULL values, the
# default is to not allow NULL values. # default is to not allow NULL values.
# #
# This method can also take a block which is passed directly to the # This method can also take a block which is passed directly to the
# `update_column_in_batches` method. # `update_column_in_batches` method.
def add_column_with_default(table, column, type, default:, allow_null: false, &block) def add_column_with_default(table, column, type, default:, limit: nil, allow_null: false, &block)
if transaction_open? if transaction_open?
raise 'add_column_with_default can not be run inside a transaction, ' \ raise 'add_column_with_default can not be run inside a transaction, ' \
'you can disable transactions by calling disable_ddl_transaction! ' \ 'you can disable transactions by calling disable_ddl_transaction! ' \
...@@ -144,7 +146,11 @@ module Gitlab ...@@ -144,7 +146,11 @@ module Gitlab
disable_statement_timeout disable_statement_timeout
transaction do transaction do
if limit
add_column(table, column, type, default: nil, limit: limit)
else
add_column(table, column, type, default: nil) add_column(table, column, type, default: nil)
end
# Changing the default before the update ensures any newly inserted # Changing the default before the update ensures any newly inserted
# rows already use the proper default value. # rows already use the proper default value.
......
...@@ -22,10 +22,6 @@ module Gitlab ...@@ -22,10 +22,6 @@ module Gitlab
private private
def repos_path
Gitlab.config.gitlab_shell.repos_path
end
def path_to_repo def path_to_repo
@project.repository.path_to_repo @project.repository.path_to_repo
end end
......
# == Schema Information
#
# Table name: runner_projects
#
# id :integer not null, primary key
# runner_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
FactoryGirl.define do FactoryGirl.define do
factory :ci_runner_project, class: Ci::RunnerProject do factory :ci_runner_project, class: Ci::RunnerProject do
runner_id 1 runner_id 1
......
# == Schema Information
#
# Table name: runners
#
# id :integer not null, primary key
# token :string(255)
# created_at :datetime
# updated_at :datetime
# description :string(255)
# contacted_at :datetime
# active :boolean default(TRUE), not null
# is_shared :boolean default(FALSE)
# name :string(255)
# version :string(255)
# revision :string(255)
# platform :string(255)
# architecture :string(255)
#
FactoryGirl.define do FactoryGirl.define do
factory :ci_runner, class: Ci::Runner do factory :ci_runner, class: Ci::Runner do
sequence :description do |n| sequence :description do |n|
......
# == Schema Information
#
# Table name: ci_variables
#
# id :integer not null, primary key
# project_id :integer not null
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
# gl_project_id :integer
#
FactoryGirl.define do FactoryGirl.define do
factory :ci_variable, class: Ci::Variable do factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" } sequence(:key) { |n| "VARIABLE_#{n}" }
......
# == Schema Information
#
# Table name: group_members
#
# id :integer not null, primary key
# group_access :integer not null
# group_id :integer not null
# user_id :integer not null
# created_at :datetime
# updated_at :datetime
# notification_level :integer default(3), not null
#
FactoryGirl.define do FactoryGirl.define do
factory :group_member do factory :group_member do
access_level { GroupMember::OWNER } access_level { GroupMember::OWNER }
......
...@@ -2,6 +2,7 @@ require 'rails_helper' ...@@ -2,6 +2,7 @@ require 'rails_helper'
describe 'Issue Boards', feature: true, js: true do describe 'Issue Boards', feature: true, js: true do
include WaitForAjax include WaitForAjax
include WaitForVueResource
let(:project) { create(:empty_project, :public) } let(:project) { create(:empty_project, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -187,13 +188,13 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -187,13 +188,13 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_content('Showing 20 of 56 issues') expect(page).to have_content('Showing 20 of 56 issues')
evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight") evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(page).to have_selector('.card', count: 40) expect(page).to have_selector('.card', count: 40)
expect(page).to have_content('Showing 40 of 56 issues') 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") evaluate_script("document.querySelectorAll('.board .board-list')[0].scrollTop = document.querySelectorAll('.board .board-list')[0].scrollHeight")
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(page).to have_selector('.card', count: 56) expect(page).to have_selector('.card', count: 56)
expect(page).to have_content('Showing all issues') expect(page).to have_content('Showing all issues')
...@@ -372,7 +373,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -372,7 +373,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.dropdown-menu-author' do page.within '.dropdown-menu-author' do
click_link(user2.name) click_link(user2.name)
end end
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(find('.js-author-search')).to have_content(user2.name) expect(find('.js-author-search')).to have_content(user2.name)
end end
...@@ -398,7 +399,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -398,7 +399,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.dropdown-menu-assignee' do page.within '.dropdown-menu-assignee' do
click_link(user.name) click_link(user.name)
end end
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(find('.js-assignee-search')).to have_content(user.name) expect(find('.js-assignee-search')).to have_content(user.name)
end end
...@@ -424,7 +425,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -424,7 +425,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.milestone-filter' do page.within '.milestone-filter' do
click_link(milestone.title) click_link(milestone.title)
end end
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(find('.js-milestone-select')).to have_content(milestone.title) expect(find('.js-milestone-select')).to have_content(milestone.title)
end end
...@@ -449,7 +450,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -449,7 +450,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.dropdown-menu-labels' do page.within '.dropdown-menu-labels' do
click_link(testing.title) click_link(testing.title)
wait_for_vue_resource(spinner: false) wait_for_vue_resource
find('.dropdown-menu-close').click find('.dropdown-menu-close').click
end end
end end
...@@ -478,7 +479,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -478,7 +479,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.dropdown-menu-labels' do page.within '.dropdown-menu-labels' do
click_link(testing.title) click_link(testing.title)
wait_for_vue_resource(spinner: false) wait_for_vue_resource
find('.dropdown-menu-close').click find('.dropdown-menu-close').click
end end
end end
...@@ -509,9 +510,9 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -509,9 +510,9 @@ describe 'Issue Boards', feature: true, js: true do
page.within(find('.dropdown-menu-labels')) do page.within(find('.dropdown-menu-labels')) do
click_link(testing.title) click_link(testing.title)
wait_for_vue_resource(spinner: false) wait_for_vue_resource
click_link(bug.title) click_link(bug.title)
wait_for_vue_resource(spinner: false) wait_for_vue_resource
find('.dropdown-menu-close').click find('.dropdown-menu-close').click
end end
end end
...@@ -536,7 +537,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -536,7 +537,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within '.dropdown-menu-labels' do page.within '.dropdown-menu-labels' do
click_link("No Label") click_link("No Label")
wait_for_vue_resource(spinner: false) wait_for_vue_resource
find('.dropdown-menu-close').click find('.dropdown-menu-close').click
end end
end end
...@@ -559,7 +560,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -559,7 +560,7 @@ describe 'Issue Boards', feature: true, js: true do
expect(page).to have_selector('.card', count: 6) expect(page).to have_selector('.card', count: 6)
expect(find('.card', match: :first)).to have_content(bug.title) expect(find('.card', match: :first)).to have_content(bug.title)
click_button(bug.title) click_button(bug.title)
wait_for_vue_resource(spinner: false) wait_for_vue_resource
end end
wait_for_vue_resource wait_for_vue_resource
...@@ -584,7 +585,7 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -584,7 +585,7 @@ describe 'Issue Boards', feature: true, js: true do
page.within(find('.card', match: :first)) do page.within(find('.card', match: :first)) do
click_button(bug.title) click_button(bug.title)
end end
wait_for_vue_resource(spinner: false) wait_for_vue_resource
expect(page).to have_selector('.card', count: 1) expect(page).to have_selector('.card', count: 1)
end end
...@@ -647,14 +648,4 @@ describe 'Issue Boards', feature: true, js: true do ...@@ -647,14 +648,4 @@ describe 'Issue Boards', feature: true, js: true do
wait_for_vue_resource wait_for_vue_resource
end end
def wait_for_vue_resource(spinner: true)
Timeout.timeout(Capybara.default_max_wait_time) do
loop until page.evaluate_script('Vue.activeResources').zero?
end
if spinner
expect(find('.boards-list')).not_to have_selector('.fa-spinner')
end
end
end end
require 'rails_helper'
describe 'Issue Boards shortcut', feature: true, js: true do
include WaitForVueResource
let(:project) { create(:empty_project) }
before do
project.create_board
project.board.lists.create(list_type: :backlog)
project.board.lists.create(list_type: :done)
login_as :admin
visit namespace_project_path(project.namespace, project)
end
it 'takes user to issue board index' do
find('body').native.send_keys('gl')
expect(page).to have_selector('.boards-list')
wait_for_vue_resource
end
end
require 'spec_helper'
feature 'Contributions Calendar', js: true, feature: true do
include WaitForAjax
let(:contributed_project) { create(:project, :public) }
before do
login_as :user
issue_params = { title: 'Bug in old browser' }
Issues::CreateService.new(contributed_project, @user, issue_params).execute
# Push code contribution
push_params = {
project: contributed_project,
action: Event::PUSHED,
author_id: @user.id,
data: { commit_count: 3 }
}
Event.create(push_params)
visit @user.username
wait_for_ajax
end
it 'displays calendar', js: true do
expect(page).to have_css('.js-contrib-calendar')
end
it 'displays calendar activity log', js: true do
expect(find('.content_list .event-note')).to have_content "Bug in old browser"
end
it 'displays calendar activity square color', js: true do
expect(page).to have_selector('.user-contrib-cell[fill=\'#acd5f2\']', count: 1)
end
end
...@@ -144,7 +144,7 @@ describe 'Issues', feature: true do ...@@ -144,7 +144,7 @@ describe 'Issues', feature: true do
visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id)
expect(page).to have_content 'foobar' expect(page).to have_content 'foobar'
expect(page.all('.issue-no-comments').first.text).to eq "0" expect(page.all('.no-comments').first.text).to eq "0"
end end
end end
......
...@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do ...@@ -13,10 +13,12 @@ feature 'issuable templates', feature: true, js: true do
context 'user creates an issue using templates' do context 'user creates an issue using templates' do
let(:template_content) { 'this is a test "bug" template' } let(:template_content) { 'this is a test "bug" template' }
let(:longtemplate_content) { %Q(this\n\n\n\n\nis\n\n\n\n\na\n\n\n\n\nbug\n\n\n\n\ntemplate) }
let(:issue) { create(:issue, author: user, assignee: user, project: project) } let(:issue) { create(:issue, author: user, assignee: user, project: project) }
background do background do
project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false) project.repository.commit_file(user, '.gitlab/issue_templates/bug.md', template_content, 'added issue template', 'master', false)
project.repository.commit_file(user, '.gitlab/issue_templates/test.md', longtemplate_content, 'added issue template', 'master', false)
visit edit_namespace_project_issue_path project.namespace, project, issue visit edit_namespace_project_issue_path project.namespace, project, issue
fill_in :'issue[title]', with: 'test issue title' fill_in :'issue[title]', with: 'test issue title'
end end
...@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do ...@@ -27,6 +29,17 @@ feature 'issuable templates', feature: true, js: true do
preview_template preview_template
save_changes save_changes
end end
it 'updates height of markdown textarea' do
start_height = page.evaluate_script('$(".markdown-area").outerHeight()')
select_template 'test'
wait_for_ajax
end_height = page.evaluate_script('$(".markdown-area").outerHeight()')
expect(end_height).not_to eq(start_height)
end
end end
context 'user creates a merge request using templates' do context 'user creates a merge request using templates' do
...@@ -51,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do ...@@ -51,7 +64,7 @@ feature 'issuable templates', feature: true, js: true do
let(:template_content) { 'this is a test "feature-proposal" template' } let(:template_content) { 'this is a test "feature-proposal" template' }
let(:fork_user) { create(:user) } let(:fork_user) { create(:user) }
let(:fork_project) { create(:project, :public) } let(:fork_project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project) } let(:merge_request) { create(:merge_request, :with_diffs, source_project: fork_project, target_project: project) }
background do background do
logout logout
...@@ -59,18 +72,22 @@ feature 'issuable templates', feature: true, js: true do ...@@ -59,18 +72,22 @@ feature 'issuable templates', feature: true, js: true do
fork_project.team << [fork_user, :master] fork_project.team << [fork_user, :master]
create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project) create(:forked_project_link, forked_to_project: fork_project, forked_from_project: project)
login_as fork_user login_as fork_user
fork_project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false) project.repository.commit_file(fork_user, '.gitlab/merge_request_templates/feature-proposal.md', template_content, 'added merge request template', 'master', false)
visit edit_namespace_project_merge_request_path fork_project.namespace, fork_project, merge_request visit edit_namespace_project_merge_request_path project.namespace, project, merge_request
fill_in :'merge_request[title]', with: 'test merge request title' fill_in :'merge_request[title]', with: 'test merge request title'
end end
scenario 'user selects "feature-proposal" template' do context 'feature proposal template' do
context 'template exists in target project' do
scenario 'user selects template' do
select_template 'feature-proposal' select_template 'feature-proposal'
wait_for_ajax wait_for_ajax
preview_template preview_template
save_changes save_changes
end end
end end
end
end
def preview_template def preview_template
click_link 'Preview' click_link 'Preview'
......
...@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -29,8 +29,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search projects', with: project_1.name_with_namespace fill_in 'Search projects', with: project_1.name_with_namespace
click_link project_1.name_with_namespace click_link project_1.name_with_namespace
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content project_2.name_with_namespace
expect(page).to have_content project_1.name_with_namespace
expect(page).not_to have_content project_2.name_with_namespace
end end
it 'filters by author' do it 'filters by author' do
...@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -39,8 +42,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
fill_in 'Search authors', with: user_1.name fill_in 'Search authors', with: user_1.name
click_link user_1.name click_link user_1.name
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content user_2.name
expect(find('.todos-list')).to have_content user_1.name
expect(find('.todos-list')).not_to have_content user_2.name
end end
it 'filters by type' do it 'filters by type' do
...@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -48,8 +54,11 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-type' do within '.dropdown-menu-type' do
click_link 'Issue' click_link 'Issue'
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content ' merge request !'
expect(find('.todos-list')).to have_content issue.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end end
it 'filters by action' do it 'filters by action' do
...@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do ...@@ -57,7 +66,10 @@ describe 'Dashboard > User filters todos', feature: true, js: true do
within '.dropdown-menu-action' do within '.dropdown-menu-action' do
click_link 'Assigned' click_link 'Assigned'
end end
wait_for_ajax wait_for_ajax
expect('.prepend-top-default').not_to have_content ' mentioned '
expect(find('.todos-list')).to have_content ' assigned you '
expect(find('.todos-list')).not_to have_content ' mentioned '
end end
end end
...@@ -19,7 +19,10 @@ describe 'Snippets tab on a user profile', feature: true, js: true do ...@@ -19,7 +19,10 @@ describe 'Snippets tab on a user profile', feature: true, js: true do
end end
context 'clicking on the link to the second page' do context 'clicking on the link to the second page' do
before { click_link('2') } before do
click_link('2')
wait_for_ajax
end
it 'shows the remaining snippets' do it 'shows the remaining snippets' do
expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5) expect(page.all('.snippets-list-holder .snippet-row').count).to eq(5)
......
...@@ -18,4 +18,67 @@ describe GroupsHelper do ...@@ -18,4 +18,67 @@ describe GroupsHelper do
expect(group_icon(group.path)).to match('group_avatar.png') expect(group_icon(group.path)).to match('group_avatar.png')
end end
end end
describe 'group_lfs_status' do
let(:group) { create(:group) }
let!(:project) { create(:empty_project, namespace_id: group.id) }
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
context 'only one project in group' do
before do
group.update_attribute(:lfs_enabled, true)
end
it 'returns all projects as enabled' do
expect(group_lfs_status(group)).to include('Enabled for all projects')
end
it 'returns all projects as disabled' do
project.update_attribute(:lfs_enabled, false)
expect(group_lfs_status(group)).to include('Enabled for 0 out of 1 project')
end
end
context 'more than one project in group' do
before do
create(:empty_project, namespace_id: group.id)
end
context 'LFS enabled in group' do
before do
group.update_attribute(:lfs_enabled, true)
end
it 'returns both projects as enabled' do
expect(group_lfs_status(group)).to include('Enabled for all projects')
end
it 'returns only one as enabled' do
project.update_attribute(:lfs_enabled, false)
expect(group_lfs_status(group)).to include('Enabled for 1 out of 2 projects')
end
end
context 'LFS disabled in group' do
before do
group.update_attribute(:lfs_enabled, false)
end
it 'returns both projects as disabled' do
expect(group_lfs_status(group)).to include('Disabled for all projects')
end
it 'returns only one as disabled' do
project.update_attribute(:lfs_enabled, true)
expect(group_lfs_status(group)).to include('Disabled for 1 out of 2 projects')
end
end
end
end
end end
...@@ -91,6 +91,7 @@ describe Gitlab::Database::MigrationHelpers, lib: true do ...@@ -91,6 +91,7 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
describe '#add_column_with_default' do describe '#add_column_with_default' do
context 'outside of a transaction' do context 'outside of a transaction' do
context 'when a column limit is not set' do
before do before do
expect(model).to receive(:transaction_open?).and_return(false) expect(model).to receive(:transaction_open?).and_return(false)
...@@ -151,6 +152,22 @@ describe Gitlab::Database::MigrationHelpers, lib: true do ...@@ -151,6 +152,22 @@ describe Gitlab::Database::MigrationHelpers, lib: true do
end end
end end
context 'when a column limit is set' do
it 'adds the column with a limit' do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:transaction).and_yield
allow(model).to receive(:update_column_in_batches).with(:projects, :foo, 10)
allow(model).to receive(:change_column_null).with(:projects, :foo, false)
allow(model).to receive(:change_column_default).with(:projects, :foo, 10)
expect(model).to receive(:add_column).
with(:projects, :foo, :integer, default: nil, limit: 8)
model.add_column_with_default(:projects, :foo, :integer, default: 10, limit: 8)
end
end
end
context 'inside a transaction' do context 'inside a transaction' do
it 'raises RuntimeError' do it 'raises RuntimeError' do
expect(model).to receive(:transaction_open?).and_return(true) expect(model).to receive(:transaction_open?).and_return(true)
......
...@@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do ...@@ -373,8 +373,8 @@ describe Ci::Pipeline, models: true do
end end
describe '#execute_hooks' do describe '#execute_hooks' do
let!(:build_a) { create_build('a') } let!(:build_a) { create_build('a', 0) }
let!(:build_b) { create_build('b') } let!(:build_b) { create_build('b', 1) }
let!(:hook) do let!(:hook) do
create(:project_hook, project: project, pipeline_events: enabled) create(:project_hook, project: project, pipeline_events: enabled)
...@@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do ...@@ -398,7 +398,7 @@ describe Ci::Pipeline, models: true do
build_b.enqueue build_b.enqueue
end end
it 'receive a pending event once' do it 'receives a pending event once' do
expect(WebMock).to have_requested_pipeline_hook('pending').once expect(WebMock).to have_requested_pipeline_hook('pending').once
end end
end end
...@@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do ...@@ -411,7 +411,7 @@ describe Ci::Pipeline, models: true do
build_b.run build_b.run
end end
it 'receive a running event once' do it 'receives a running event once' do
expect(WebMock).to have_requested_pipeline_hook('running').once expect(WebMock).to have_requested_pipeline_hook('running').once
end end
end end
...@@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do ...@@ -422,11 +422,21 @@ describe Ci::Pipeline, models: true do
build_b.success build_b.success
end end
it 'receive a success event once' do it 'receives a success event once' do
expect(WebMock).to have_requested_pipeline_hook('success').once expect(WebMock).to have_requested_pipeline_hook('success').once
end end
end end
context 'when stage one failed' do
before do
build_a.drop
end
it 'receives a failed event once' do
expect(WebMock).to have_requested_pipeline_hook('failed').once
end
end
def have_requested_pipeline_hook(status) def have_requested_pipeline_hook(status)
have_requested(:post, hook.url).with do |req| have_requested(:post, hook.url).with do |req|
json_body = JSON.parse(req.body) json_body = JSON.parse(req.body)
...@@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do ...@@ -450,8 +460,12 @@ describe Ci::Pipeline, models: true do
end end
end end
def create_build(name) def create_build(name, stage_idx)
create(:ci_build, :created, pipeline: pipeline, name: name) create(:ci_build,
:created,
pipeline: pipeline,
name: name,
stage_idx: stage_idx)
end end
end end
end end
...@@ -40,7 +40,7 @@ describe CommitStatus, models: true do ...@@ -40,7 +40,7 @@ describe CommitStatus, models: true do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
%w(running success failed).each do |status| %w[running success failed].each do |status|
context "if commit status is #{status}" do context "if commit status is #{status}" do
before { commit_status.status = status } before { commit_status.status = status }
...@@ -48,7 +48,7 @@ describe CommitStatus, models: true do ...@@ -48,7 +48,7 @@ describe CommitStatus, models: true do
end end
end end
%w(pending canceled).each do |status| %w[pending canceled].each do |status|
context "if commit status is #{status}" do context "if commit status is #{status}" do
before { commit_status.status = status } before { commit_status.status = status }
...@@ -60,7 +60,7 @@ describe CommitStatus, models: true do ...@@ -60,7 +60,7 @@ describe CommitStatus, models: true do
describe '#active?' do describe '#active?' do
subject { commit_status.active? } subject { commit_status.active? }
%w(pending running).each do |state| %w[pending running].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -68,7 +68,7 @@ describe CommitStatus, models: true do ...@@ -68,7 +68,7 @@ describe CommitStatus, models: true do
end end
end end
%w(success failed canceled).each do |state| %w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -80,7 +80,7 @@ describe CommitStatus, models: true do ...@@ -80,7 +80,7 @@ describe CommitStatus, models: true do
describe '#complete?' do describe '#complete?' do
subject { commit_status.complete? } subject { commit_status.complete? }
%w(success failed canceled).each do |state| %w[success failed canceled].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -88,7 +88,7 @@ describe CommitStatus, models: true do ...@@ -88,7 +88,7 @@ describe CommitStatus, models: true do
end end
end end
%w(pending running).each do |state| %w[pending running].each do |state|
context "if commit_status.status is #{state}" do context "if commit_status.status is #{state}" do
before { commit_status.status = state } before { commit_status.status = state }
...@@ -187,7 +187,7 @@ describe CommitStatus, models: true do ...@@ -187,7 +187,7 @@ describe CommitStatus, models: true do
subject { CommitStatus.where(pipeline: pipeline).stages } subject { CommitStatus.where(pipeline: pipeline).stages }
it 'returns ordered list of stages' do it 'returns ordered list of stages' do
is_expected.to eq(%w(build test deploy)) is_expected.to eq(%w[build test deploy])
end end
end end
...@@ -223,4 +223,33 @@ describe CommitStatus, models: true do ...@@ -223,4 +223,33 @@ describe CommitStatus, models: true do
expect(commit_status.commit).to eq project.commit expect(commit_status.commit).to eq project.commit
end end
end end
describe '#group_name' do
subject { commit_status.group_name }
tests = {
'rspec:windows' => 'rspec:windows',
'rspec:windows 0' => 'rspec:windows 0',
'rspec:windows 0 test' => 'rspec:windows 0 test',
'rspec:windows 0 1' => 'rspec:windows',
'rspec:windows 0 1 name' => 'rspec:windows name',
'rspec:windows 0/1' => 'rspec:windows',
'rspec:windows 0/1 name' => 'rspec:windows name',
'rspec:windows 0:1' => 'rspec:windows',
'rspec:windows 0:1 name' => 'rspec:windows name',
'rspec:windows 10000 20000' => 'rspec:windows',
'rspec:windows 0 : / 1' => 'rspec:windows',
'rspec:windows 0 : / 1 name' => 'rspec:windows name',
'0 1 name ruby' => 'name ruby',
'0 :/ 1 name ruby' => 'name ruby'
}
tests.each do |name, group_name|
it "'#{name}' puts in '#{group_name}'" do
commit_status.name = name
is_expected.to eq(group_name)
end
end
end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment