diff options
author | Regis <boudinot.regis@yahoo.com> | 2017-01-02 16:24:37 -0700 |
---|---|---|
committer | Regis <boudinot.regis@yahoo.com> | 2017-01-02 16:24:37 -0700 |
commit | 0a074f2e091d8b2f1bce49e50ecf69b667c62dc2 (patch) | |
tree | 314027556f06514278a4f83f35813c00b2d5418f | |
parent | 588219352c99213d099aff72f32d6ad9ec4830d4 (diff) | |
parent | de25604fbca2f7005754d821d571bbcb1cc510ac (diff) | |
download | gitlab-ce-0a074f2e091d8b2f1bce49e50ecf69b667c62dc2.tar.gz |
fix pipelines/index.html.haml merge conflict
473 files changed, 3660 insertions, 1563 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b256e8a2a5f..e0e780e1e6b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -425,7 +425,7 @@ notify:slack: SETUP_DB: "false" USE_BUNDLE_INSTALL: "false" script: - - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/builds>" + - ./scripts/notify_slack.sh "#development" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See <https://gitlab.com/gitlab-org/$(basename "$PWD")/commit/"$CI_BUILD_REF"/pipelines>" when: on_failure only: - master@gitlab-org/gitlab-ce diff --git a/.haml-lint.yml b/.haml-lint.yml index da9a43d9c6d..7c8a9c4fd17 100644 --- a/.haml-lint.yml +++ b/.haml-lint.yml @@ -7,10 +7,10 @@ exclude: linters: AltText: - enabled: false + enabled: true ClassAttributeWithStaticValue: - enabled: false + enabled: true ClassesBeforeIds: enabled: false @@ -29,14 +29,14 @@ linters: enabled: true FinalNewline: - enabled: false + enabled: true present: true HtmlAttributes: - enabled: false + enabled: true ImplicitDiv: - enabled: false + enabled: true LeadingCommentSpace: enabled: false @@ -80,10 +80,10 @@ linters: enabled: false SpaceBeforeScript: - enabled: false + enabled: true SpaceInsideHashAttributes: - enabled: false + enabled: true style: space Indentation: @@ -94,7 +94,7 @@ linters: enabled: true TrailingWhitespace: - enabled: false + enabled: true UnnecessaryInterpolation: enabled: false diff --git a/CHANGELOG.md b/CHANGELOG.md index c57ba82e38c..1baf5b3257c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.15.2 (2016-12-27) + +- Fix finding the latest pipeline. !8301 +- Fix mr list timestamp alignment. !8271 +- Fix discussion overlap text in regular screens. !8273 +- Fixes mini-pipeline-graph dropdown animation and stage position in chrome, firefox and safari. !8282 +- Fix line breaking in nodes of the pipeline graph in firefox. !8292 +- Fixes confendential warning text alignment. !8293 +- Hide Scroll Top button for failed build page. !8295 +- Fix finding the latest pipeline. !8301 +- Disable PostgreSQL statement timeouts when removing unneeded services. !8322 +- Fix timeout when MR contains large files marked as binary by .gitattributes. +- Rename "autodeploy" to "auto deploy". +- Fixed GFM autocomplete error when no data exists. +- Fixed resolve discussion note button color. + ## 8.15.1 (2016-12-23) - Push payloads schedule at most 100 commits, instead of all commits. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 659871a06a4..b68c4a67826 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -217,8 +217,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the label [`Accepting Merge Requests` on our issue tracker for CE][accepting-mrs-ce] and [EE][accepting-mrs-ee] but other improvements are also welcome. Please note -that if an issue is marked for the current milestone either before or while you -are working on it, a team member may take over the merge request in order to +that if an issue is marked for the current milestone either before or while you +are working on it, a team member may take over the merge request in order to ensure the work is finished before the release date. If you want to add a new feature that is not labeled it is best to first create @@ -300,6 +300,7 @@ you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MRs that leads to higher code quality is more important to us than having a minimal commit log. The smaller an MR is the more likely it is it will be merged (quickly). After that you can send more MRs to enhance it. +The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this. For examples of feedback on merge requests please look at already [closed merge requests][closed-merge-requests]. If you would like quick feedback @@ -332,7 +332,7 @@ gem 'octokit', '~> 4.3.0' gem 'mail_room', '~> 0.9.0' -gem 'email_reply_parser', '~> 0.5.8' +gem 'email_reply_trimmer', '~> 0.1' gem 'html2text' gem 'ruby-prof', '~> 0.16.2' @@ -347,5 +347,5 @@ gem 'paranoia', '~> 2.2' gem 'health_check', '~> 2.2.0' # System information -gem 'vmstat', '~> 2.2' +gem 'vmstat', '~> 2.3.0' gem 'sys-filesystem', '~> 1.1.6' diff --git a/Gemfile.lock b/Gemfile.lock index 9f8367b420a..1c192d2fc6e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,7 +167,7 @@ GEM railties (>= 4.2) dropzonejs-rails (0.7.2) rails (> 3.1) - email_reply_parser (0.5.8) + email_reply_trimmer (0.1.6) email_spec (1.6.0) launchy (~> 2.1) mail (~> 2.2) @@ -773,7 +773,7 @@ GEM coercible (~> 1.0) descendants_tracker (~> 0.0, >= 0.0.3) equalizer (~> 0.0, >= 0.0.9) - vmstat (2.2.0) + vmstat (2.3.0) warden (1.2.6) rack (>= 1.0) web-console (2.3.0) @@ -839,7 +839,7 @@ DEPENDENCIES diffy (~> 3.1.0) doorkeeper (~> 4.2.0) dropzonejs-rails (~> 0.7.1) - email_reply_parser (~> 0.5.8) + email_reply_trimmer (~> 0.1) email_spec (~> 1.6.0) factory_girl_rails (~> 4.7.0) ffaker (~> 2.0.0) @@ -982,7 +982,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.4) version_sorter (~> 2.1.0) virtus (~> 1.0.1) - vmstat (~> 2.2) + vmstat (~> 2.3.0) web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/PROCESS.md b/PROCESS.md index 8af660fbdd1..cbeb781cd3c 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -69,7 +69,8 @@ to add details to the issue. - ~UX needs help from a UX designer - ~Frontend needs help from a Front-end engineer. Please follow the ["Implement design & UI elements" guidelines]. -- ~up-for-grabs is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels. +- ~"Accepting Merge Requests" is a low priority, well-defined issue that we + encourage people to contribute to. Not exclusive with other labels. - ~"feature proposal" is a proposal for a new feature for GitLab. People are encouraged to vote in support or comment for further detail. Do not use `feature request`. - ~bug is an issue reporting undesirable or incorrect behavior. diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 043c6a11c4f..e43afbb4cc9 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -89,6 +89,14 @@ // Set the default path for all cookies to GitLab's root directory Cookies.defaults.path = gon.relative_url_root || '/'; + // `hashchange` is not triggered when link target is already in window.location + $body.on('click', 'a[href^="#"]', function() { + var href = this.getAttribute('href'); + if (href.substr(1) === gl.utils.getLocationHash()) { + setTimeout(gl.utils.handleLocationHash, 1); + } + }); + // prevent default action for disabled buttons $('.btn').click(function(e) { if ($(this).hasClass('disabled')) { diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index ab2343c72fc..2c9ab61c94d 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -73,7 +73,7 @@ $(() => { }); gl.IssueBoardsSearch = new Vue({ - el: '#js-boards-seach', + el: '#js-boards-search', data: { filters: Store.state.filters }, diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js.es6 index 1199e022ff1..cd942c8332b 100644 --- a/app/assets/javascripts/boards/models/issue.js.es6 +++ b/app/assets/javascripts/boards/models/issue.js.es6 @@ -71,3 +71,5 @@ class ListIssue { return Vue.http.patch(url, data); } } + +window.ListIssue = ListIssue; diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js.es6 index 8f20a1bbec7..9af88d167d6 100644 --- a/app/assets/javascripts/boards/models/label.js.es6 +++ b/app/assets/javascripts/boards/models/label.js.es6 @@ -10,3 +10,5 @@ class ListLabel { this.priority = (obj.priority !== null) ? obj.priority : Infinity; } } + +window.ListLabel = ListLabel; diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js.es6 index a8d38c16485..fb13f529b11 100644 --- a/app/assets/javascripts/boards/models/list.js.es6 +++ b/app/assets/javascripts/boards/models/list.js.es6 @@ -148,3 +148,5 @@ class List { }); } } + +window.List = List; diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js.es6 index 9c173c1b70b..c867b06d320 100644 --- a/app/assets/javascripts/boards/models/milestone.js.es6 +++ b/app/assets/javascripts/boards/models/milestone.js.es6 @@ -6,3 +6,5 @@ class ListMilestone { this.title = obj.title; } } + +window.ListMilestone = ListMilestone; diff --git a/app/assets/javascripts/boards/models/user.js.es6 b/app/assets/javascripts/boards/models/user.js.es6 index a8a3892e2ad..8e9de4d4cbb 100644 --- a/app/assets/javascripts/boards/models/user.js.es6 +++ b/app/assets/javascripts/boards/models/user.js.es6 @@ -8,3 +8,5 @@ class ListUser { this.avatar = user.avatar_url; } } + +window.ListUser = ListUser; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 189a8703198..f18633f0913 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -65,4 +65,6 @@ class BoardService { issue }); } -}; +} + +window.BoardService = BoardService; diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index 5e449170cd3..bc13c46443a 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -92,8 +92,8 @@ success: function(buildData) { $('.js-build-output').html(buildData.trace_html); if (removeRefreshStatuses.indexOf(buildData.status) >= 0) { - this.initScrollMonitor(); - return this.$buildRefreshAnimation.remove(); + this.$buildRefreshAnimation.remove(); + return this.initScrollMonitor(); } }.bind(this) }); diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 index 88a19fc6e1d..5852b8bbdb7 100644 --- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 +++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 @@ -59,9 +59,11 @@ }, methods: { updateTooltip: function () { - $(this.$refs.button) - .tooltip('hide') - .tooltip('fixTitle'); + this.$nextTick(() => { + $(this.$refs.button) + .tooltip('hide') + .tooltip('fixTitle'); + }); }, resolve: function () { if (!this.canResolve) return; @@ -90,7 +92,7 @@ new Flash('An error occurred when trying to resolve a comment. Please try again.', 'alert'); } - this.$nextTick(this.updateTooltip); + this.updateTooltip(); }); } }, diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js.es6 index efcd46680a7..fa518ba4d33 100644 --- a/app/assets/javascripts/diff_notes/models/discussion.js.es6 +++ b/app/assets/javascripts/diff_notes/models/discussion.js.es6 @@ -92,3 +92,5 @@ class DiscussionModel { return false; } } + +window.DiscussionModel = DiscussionModel; diff --git a/app/assets/javascripts/diff_notes/models/note.js.es6 b/app/assets/javascripts/diff_notes/models/note.js.es6 index e3bce1d2038..f3a7cba5ef6 100644 --- a/app/assets/javascripts/diff_notes/models/note.js.es6 +++ b/app/assets/javascripts/diff_notes/models/note.js.es6 @@ -9,3 +9,5 @@ class NoteModel { this.resolved_by = resolved_by; } } + +window.NoteModel = NoteModel; diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 6a84f52d8df..f09f496710f 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -64,6 +64,17 @@ new UsernameValidator(); new ActiveTabMemoizer(); break; + case 'sessions:create': + if (!gon.u2f) break; + window.gl.u2fAuthenticate = new gl.U2FAuthenticate( + $("#js-authenticate-u2f"), + '#js-login-u2f-form', + gon.u2f, + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form'), + ); + window.gl.u2fAuthenticate.start(); + break; case 'projects:boards:show': case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js.es6 index 15ec7b76c3d..575a45d9802 100644 --- a/app/assets/javascripts/environments/services/environments_service.js.es6 +++ b/app/assets/javascripts/environments/services/environments_service.js.es6 @@ -20,3 +20,5 @@ class EnvironmentsService { return this.environments.get(); } } + +window.EnvironmentsService = EnvironmentsService; diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index 12875eaa1c3..87c579ac757 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -77,7 +77,7 @@ var _a, _y, regexp, match, atSymbolsWithBar, atSymbolsWithoutBar; atSymbolsWithBar = Object.keys(this.app.controllers).join('|'); atSymbolsWithoutBar = Object.keys(this.app.controllers).join(''); - subtext = subtext.split(' ').pop(); + subtext = subtext.split(/\s+/g).pop(); flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); _a = decodeURI("%C3%80"); @@ -367,7 +367,7 @@ return $input.trigger('keyup'); }, isLoading(data) { - if (!data) return false; + if (!data || !data.length) return false; if (Array.isArray(data)) data = data[0]; return data === this.defaultLoadingData[0] || data.name === this.defaultLoadingData[0]; }, diff --git a/app/assets/javascripts/issue.js b/app/assets/javascripts/issue.js index 63b70d4be17..61e8531153b 100644 --- a/app/assets/javascripts/issue.js +++ b/app/assets/javascripts/issue.js @@ -139,15 +139,12 @@ return; } return $.getJSON($container.data('path')).error(function() { - $container.find('.checking').hide(); $container.find('.unavailable').show(); return new Flash('Failed to check if a new branch can be created.', 'alert'); }).success(function(data) { if (data.can_create_branch) { - $container.find('.checking').hide(); $container.find('.available').show(); } else { - $container.find('.checking').hide(); return $container.find('.unavailable').show(); } }); diff --git a/app/assets/javascripts/preview_markdown.js b/app/assets/javascripts/preview_markdown.js index 1e261cd49c2..89f7e976934 100644 --- a/app/assets/javascripts/preview_markdown.js +++ b/app/assets/javascripts/preview_markdown.js @@ -1,14 +1,17 @@ -/* eslint-disable func-names, space-before-function-paren, no-var, one-var, one-var-declaration-per-line, wrap-iife, no-else-return, consistent-return, object-shorthand, comma-dangle, no-param-reassign, padded-blocks, camelcase, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, no-var, object-shorthand, comma-dangle, prefer-arrow-callback */ // MarkdownPreview // // Handles toggling the "Write" and "Preview" tab clicks, rendering the preview, // and showing a warning when more than `x` users are referenced. // -(function() { - var lastTextareaPreviewed, markdownPreview, previewButtonSelector, writeButtonSelector; +(function () { + var lastTextareaPreviewed; + var markdownPreview; + var previewButtonSelector; + var writeButtonSelector; - window.MarkdownPreview = (function() { + window.MarkdownPreview = (function () { function MarkdownPreview() {} // Minimum number of users referenced before triggering a warning @@ -16,73 +19,71 @@ MarkdownPreview.prototype.ajaxCache = {}; - MarkdownPreview.prototype.showPreview = function(form) { - var mdText, preview; - preview = form.find('.js-md-preview'); - mdText = form.find('textarea.markdown-area').val(); + MarkdownPreview.prototype.showPreview = function ($form) { + var mdText; + var preview = $form.find('.js-md-preview'); + if (preview.hasClass('md-preview-loading')) { + return; + } + mdText = $form.find('textarea.markdown-area').val(); + if (mdText.trim().length === 0) { preview.text('Nothing to preview.'); - return this.hideReferencedUsers(form); + this.hideReferencedUsers($form); } else { - preview.text('Loading...'); - return this.renderMarkdown(mdText, (function(_this) { - return function(response) { - preview.html(response.body); - preview.renderGFM(); - return _this.renderReferencedUsers(response.references.users, form); - }; - })(this)); + preview.addClass('md-preview-loading').text('Loading...'); + this.fetchMarkdownPreview(mdText, (function (response) { + preview.removeClass('md-preview-loading').html(response.body); + preview.renderGFM(); + this.renderReferencedUsers(response.references.users, $form); + }).bind(this)); } }; - MarkdownPreview.prototype.renderMarkdown = function(text, success) { + MarkdownPreview.prototype.fetchMarkdownPreview = function (text, success) { if (!window.preview_markdown_path) { return; } if (text === this.ajaxCache.text) { - return success(this.ajaxCache.response); + success(this.ajaxCache.response); + return; } - return $.ajax({ + $.ajax({ type: 'POST', url: window.preview_markdown_path, data: { text: text }, dataType: 'json', - success: (function(_this) { - return function(response) { - _this.ajaxCache = { - text: text, - response: response - }; - return success(response); + success: (function (response) { + this.ajaxCache = { + text: text, + response: response }; - })(this) + success(response); + }).bind(this) }); }; - MarkdownPreview.prototype.hideReferencedUsers = function(form) { - var referencedUsers; - referencedUsers = form.find('.referenced-users'); - return referencedUsers.hide(); + MarkdownPreview.prototype.hideReferencedUsers = function ($form) { + $form.find('.referenced-users').hide(); }; - MarkdownPreview.prototype.renderReferencedUsers = function(users, form) { + MarkdownPreview.prototype.renderReferencedUsers = function (users, $form) { var referencedUsers; - referencedUsers = form.find('.referenced-users'); + referencedUsers = $form.find('.referenced-users'); if (referencedUsers.length) { if (users.length >= this.referenceThreshold) { referencedUsers.show(); - return referencedUsers.find('.js-referenced-users-count').text(users.length); + referencedUsers.find('.js-referenced-users-count').text(users.length); } else { - return referencedUsers.hide(); + referencedUsers.hide(); } } }; return MarkdownPreview; - - })(); + }()); markdownPreview = new window.MarkdownPreview(); @@ -92,19 +93,14 @@ lastTextareaPreviewed = null; - $.fn.setupMarkdownPreview = function() { - var $form, form_textarea; - $form = $(this); - form_textarea = $form.find('textarea.markdown-area'); - form_textarea.on('input', function() { - return markdownPreview.hideReferencedUsers($form); - }); - return form_textarea.on('blur', function() { - return markdownPreview.showPreview($form); + $.fn.setupMarkdownPreview = function () { + var $form = $(this); + $form.find('textarea.markdown-area').on('input', function () { + markdownPreview.hideReferencedUsers($form); }); }; - $(document).on('markdown-preview:show', function(e, $form) { + $(document).on('markdown-preview:show', function (e, $form) { if (!$form) { return; } @@ -115,10 +111,10 @@ // toggle content $form.find('.md-write-holder').hide(); $form.find('.md-preview-holder').show(); - return markdownPreview.showPreview($form); + markdownPreview.showPreview($form); }); - $(document).on('markdown-preview:hide', function(e, $form) { + $(document).on('markdown-preview:hide', function (e, $form) { if (!$form) { return; } @@ -129,34 +125,33 @@ // toggle content $form.find('.md-write-holder').show(); $form.find('textarea.markdown-area').focus(); - return $form.find('.md-preview-holder').hide(); + $form.find('.md-preview-holder').hide(); }); - $(document).on('markdown-preview:toggle', function(e, keyboardEvent) { + $(document).on('markdown-preview:toggle', function (e, keyboardEvent) { var $target; $target = $(keyboardEvent.target); if ($target.is('textarea.markdown-area')) { $(document).triggerHandler('markdown-preview:show', [$target.closest('form')]); - return keyboardEvent.preventDefault(); + keyboardEvent.preventDefault(); } else if (lastTextareaPreviewed) { $target = lastTextareaPreviewed; $(document).triggerHandler('markdown-preview:hide', [$target.closest('form')]); - return keyboardEvent.preventDefault(); + keyboardEvent.preventDefault(); } }); - $(document).on('click', previewButtonSelector, function(e) { + $(document).on('click', previewButtonSelector, function (e) { var $form; e.preventDefault(); $form = $(this).closest('form'); - return $(document).triggerHandler('markdown-preview:show', [$form]); + $(document).triggerHandler('markdown-preview:show', [$form]); }); - $(document).on('click', writeButtonSelector, function(e) { + $(document).on('click', writeButtonSelector, function (e) { var $form; e.preventDefault(); $form = $(this).closest('form'); - return $(document).triggerHandler('markdown-preview:hide', [$form]); + $(document).triggerHandler('markdown-preview:hide', [$form]); }); - -}).call(this); +}()); diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index eb2fe3477a2..aef2e3a3fa8 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -41,15 +41,12 @@ } beforeUpdateUsername() { - $('.loading-username').show(); - $(this).find('.update-success').hide(); - return $(this).find('.update-failed').hide(); + $('.loading-username', this).removeClass('hidden'); } afterUpdateUsername() { - $('.loading-username').hide(); - $(this).find('.btn-save').enable(); - return $(this).find('.loading-gif').hide(); + $('.loading-username', this).addClass('hidden'); + $('button[type=submit]', this).enable(); } onUpdateNotifs(e, data) { diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 650996700ba..38bc2e1c3a0 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -15,6 +15,7 @@ }, data: function(term, callback) { var finalCallback, projectsCallback; + var orderBy = $dropdown.data('order-by'); finalCallback = function(projects) { return callback(projects); }; @@ -34,7 +35,7 @@ if (this.groupId) { return Api.groupProjects(this.groupId, term, projectsCallback); } else { - return Api.projects(term, this.orderBy, projectsCallback); + return Api.projects(term, orderBy, projectsCallback); } }, url: function(project) { diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 index 08264ad3d2f..03f4531abf5 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 +++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 @@ -76,3 +76,5 @@ class ProtectedBranchDropdown { this.$dropdownFooter.toggleClass('hidden', !branchName); } } + +window.ProtectedBranchDropdown = ProtectedBranchDropdown; diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js.es6 index e407b856e10..2b992109a8c 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js.es6 @@ -8,21 +8,26 @@ // State Flow #1: setup -> in_progress -> authenticated -> POST to server // State Flow #2: setup -> in_progress -> error -> setup (function() { + const global = window.gl || (window.gl = {}); + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - this.U2FAuthenticate = (function() { - function U2FAuthenticate(container, u2fParams) { + global.U2FAuthenticate = (function() { + function U2FAuthenticate(container, form, u2fParams, fallbackButton, fallbackUI) { this.container = container; this.renderNotSupported = bind(this.renderNotSupported, this); this.renderAuthenticated = bind(this.renderAuthenticated, this); this.renderError = bind(this.renderError, this); this.renderInProgress = bind(this.renderInProgress, this); - this.renderSetup = bind(this.renderSetup, this); this.renderTemplate = bind(this.renderTemplate, this); this.authenticate = bind(this.authenticate, this); this.start = bind(this.start, this); this.appId = u2fParams.app_id; this.challenge = u2fParams.challenge; + this.form = form; + this.fallbackButton = fallbackButton; + this.fallbackUI = fallbackUI; + if (this.fallbackButton) this.fallbackButton.addEventListener('click', this.switchToFallbackUI.bind(this)); this.signRequests = u2fParams.sign_requests.map(function(request) { // The U2F Javascript API v1.1 requires a single challenge, with // _no challenges per-request_. The U2F Javascript API v1.0 requires a @@ -41,7 +46,7 @@ U2FAuthenticate.prototype.start = function() { if (U2FUtil.isU2FSupported()) { - return this.renderSetup(); + return this.renderInProgress(); } else { return this.renderNotSupported(); } @@ -77,11 +82,6 @@ return this.container.html(template(params)); }; - U2FAuthenticate.prototype.renderSetup = function() { - this.renderTemplate('setup'); - return this.container.find('#js-login-u2f-device').on('click', this.renderInProgress); - }; - U2FAuthenticate.prototype.renderInProgress = function() { this.renderTemplate('inProgress'); return this.authenticate(); @@ -92,22 +92,29 @@ error_message: error.message(), error_code: error.errorCode }); - return this.container.find('#js-u2f-try-again').on('click', this.renderSetup); + return this.container.find('#js-u2f-try-again').on('click', this.renderInProgress); }; U2FAuthenticate.prototype.renderAuthenticated = function(deviceResponse) { this.renderTemplate('authenticated'); - // Prefer to do this instead of interpolating using Underscore templates - // because of JSON escaping issues. - return this.container.find("#js-device-response").val(deviceResponse); + const container = this.container[0]; + container.querySelector('#js-device-response').value = deviceResponse; + container.querySelector(this.form).submit(); + this.fallbackButton.classList.add('hidden'); }; U2FAuthenticate.prototype.renderNotSupported = function() { return this.renderTemplate('notSupported'); }; + U2FAuthenticate.prototype.switchToFallbackUI = function() { + this.fallbackButton.classList.add('hidden'); + this.container[0].classList.add('hidden'); + this.fallbackUI.classList.remove('hidden'); + }; + return U2FAuthenticate; })(); -}).call(this); +})(); diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 251e43d2edd..67b5aa37ae7 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -26,6 +26,7 @@ .append-bottom-default { margin-bottom: $gl-padding; } .inline { display: inline-block; } .center { text-align: center; } +.vertical-align-middle { vertical-align: middle; } .underlined-link { text-decoration: underline; } .hint { font-style: italic; color: $hint-color; } @@ -57,16 +58,33 @@ pre { border-radius: 0; color: $well-pre-color; } + + &.wrap { + word-break: break-word; + white-space: pre-wrap; + } } hr { margin: $gl-padding 0; + border-top: 1px solid darken($gray-normal, 8%); } .str-truncated { @include str-truncated; } +.block-truncated { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + > div, + .str-truncated { + display: inline; + } +} + .item-title { font-weight: 600; } /** FLASH message **/ diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index 5cd242af91d..d6566dc4ec9 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -80,29 +80,32 @@ } } + .about-gitlab { + color: $color-light; + } } } } -$theme-charcoal: #3d454d; -$theme-charcoal-light: #485157; -$theme-charcoal-dark: #383f45; -$theme-charcoal-text: #b9bbbe; +$theme-charcoal-light: #b9bbbe; +$theme-charcoal: #485157; +$theme-charcoal-dark: #3d454d; +$theme-charcoal-darker: #383f45; $theme-blue-light: #becde9; $theme-blue: #2980b9; $theme-blue-dark: #1970a9; $theme-blue-darker: #096099; -$theme-graphite-lighter: #ccc; -$theme-graphite-light: #777; -$theme-graphite: #666; -$theme-graphite-dark: #555; +$theme-graphite-light: #ccc; +$theme-graphite: #777; +$theme-graphite-dark: #666; +$theme-graphite-darker: #555; -$theme-gray-light: #979797; -$theme-gray: #373737; -$theme-gray-dark: #272727; -$theme-gray-darker: #222; +$theme-black-light: #979797; +$theme-black: #373737; +$theme-black-dark: #272727; +$theme-black-darker: #222; $theme-green-light: #adc; $theme-green: #019875; @@ -120,15 +123,15 @@ body { } &.ui_charcoal { - @include gitlab-theme($theme-charcoal-text, $theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark); + @include gitlab-theme($theme-charcoal-light, $theme-charcoal, $theme-charcoal-dark, $theme-charcoal-darker); } &.ui_graphite { - @include gitlab-theme($theme-graphite-lighter, $theme-graphite-light, $theme-graphite, $theme-graphite-dark); + @include gitlab-theme($theme-graphite-light, $theme-graphite, $theme-graphite-dark, $theme-graphite-darker); } - &.ui_gray { - @include gitlab-theme($theme-gray-light, $theme-gray, $theme-gray-dark, $theme-gray-darker); + &.ui_black { + @include gitlab-theme($theme-black-light, $theme-black, $theme-black-dark, $theme-black-darker); } &.ui_green { diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index e96cd671e34..277d4202950 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -199,6 +199,7 @@ ul.content-list { display: -webkit-flex; display: -ms-flexbox; display: flex; + align-items: center; white-space: nowrap; } @@ -208,6 +209,11 @@ ul.content-list { padding-right: 8px; } + .row-fixed-content { + flex: 0 0 auto; + margin-left: auto; + } + .row-title { font-weight: 600; } @@ -239,7 +245,6 @@ ul.content-list { } ul.controls { - padding-top: 1px; float: right; list-style: none; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index bbf9de06630..5abda13a963 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -116,8 +116,8 @@ padding-top: 16px; padding-bottom: 11px; display: inline-block; - width: 50%; line-height: 28px; + white-space: normal; /* Small devices (phones, tablets, 768px and lower) */ @media (max-width: $screen-xs-max) { @@ -158,30 +158,24 @@ } .nav-controls { - width: 50%; display: inline-block; float: right; text-align: right; padding: 11px 0; margin-bottom: 0; - > .dropdown { - margin-right: $gl-padding-top; - display: inline-block; - vertical-align: top; - - &:last-child { - margin-right: 0; - } - } - - > .btn { + > .btn, + > .btn-container, + > .dropdown, + > input, + > form { margin-right: $gl-padding-top; display: inline-block; vertical-align: top; &:last-child { margin-right: 0; + float: right; } } @@ -189,19 +183,21 @@ float: none; } - > form { - display: inline-block; - } - .icon-label { display: none; } - input { + .btn, + .dropdown, + .dropdown-toggle, + input, + form { height: 35px; + } + + input { display: inline-block; position: relative; - margin-right: $gl-padding-top; /* Medium devices (desktops, 992px and up) */ @media (min-width: $screen-md-min) { width: 200px; } @@ -225,6 +221,7 @@ .btn, form, .dropdown, + .dropdown-toggle, .dropdown-menu-toggle, .form-control { margin: 0 0 10px; @@ -263,6 +260,10 @@ .nav-text, .nav-controls { width: auto; + + @media (max-width: $screen-xs-max) { + width: 100%; + } } } @@ -275,6 +276,10 @@ padding: 17px 0; } } + + pre { + width: 100%; + } } .layout-nav { @@ -427,4 +432,41 @@ border-bottom: none; } } -}
\ No newline at end of file +} + +@media (max-width: $screen-xs-max) { + .top-area { + flex-flow: row wrap; + + .nav-controls { + $controls-margin: $btn-xs-side-margin - 2px; + flex: 0 0 100%; + + &.controls-flex { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: center; + padding: 0 0 $gl-padding-top; + } + + .controls-item, + .controls-item-full, + .controls-item:last-child { + flex: 1 1 35%; + display: block; + width: 100%; + margin: $controls-margin; + + .btn, + .dropdown { + margin: 0; + } + } + + .controls-item-full { + flex: 1 1 100%; + } + } + } +} diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 46a06cd7eab..a8641e83154 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -101,6 +101,17 @@ padding: 0 8px; border-radius: 6px; } + + .about-gitlab { + padding: 7px $gl-sidebar-padding; + font-size: $gl-font-size; + line-height: 24px; + display: block; + text-decoration: none; + font-weight: normal; + position: absolute; + bottom: 10px; + } } .sidebar-action-buttons { diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 55bc325b858..28cbae9a449 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -91,7 +91,7 @@ // Labels .label { padding: 4px 5px; - font-size: 13px; + font-size: 12px; font-style: normal; font-weight: normal; display: inline-block; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 3e1fded6b6b..b0c4a6edf57 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -489,9 +489,9 @@ $project-network-controls-color: #888; */ $runner-state-shared-bg: #32b186; $runner-state-specific-bg: #3498db; -$runner-status-online-color: green; -$runner-status-offline-color: gray; -$runner-status-paused-color: red; +$runner-status-online-color: $green-normal; +$runner-status-offline-color: $gray-darkest; +$runner-status-paused-color: $red-normal; /* Stat Graph diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index c735f104c20..76a88d96183 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -31,7 +31,7 @@ .dropdown-content { max-height: 150px; - } + } } .issue-board-dropdown-content { @@ -109,6 +109,12 @@ &.has-border { border-top: 3px solid; + margin-top: -1px; + margin-right: -1px; + margin-left: -1px; + padding-top: 1px; + padding-right: 1px; + padding-left: 1px; .board-title { padding-top: ($gl-padding - 3px); diff --git a/app/assets/stylesheets/pages/deploy_keys.scss b/app/assets/stylesheets/pages/deploy_keys.scss new file mode 100644 index 00000000000..2fafe052106 --- /dev/null +++ b/app/assets/stylesheets/pages/deploy_keys.scss @@ -0,0 +1,13 @@ +.deploy-keys-list { + width: 100%; + overflow: auto; + + table { + border: 1px solid $table-border-color; + } +} + +.deploy-keys-title { + padding-bottom: 2px; + line-height: 2; +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index eeb5b590625..6b4d1f85564 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -1,50 +1,3 @@ -// Limit MR description for side-by-side diff view -.limit-container-width { - .detail-page-header { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - - .issuable-details { - .detail-page-description, - .mr-source-target, - .mr-state-widget, - .merge-manually { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - - .merge-request-tabs-holder { - &.affix { - border-bottom: 1px solid $border-color; - - .nav-links { - border: 0; - } - } - - .container-fluid { - padding-left: 0; - padding-right: 0; - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - } - } - - .diffs { - .mr-version-controls, - .files-changed { - max-width: calc(#{$limited-layout-width} - (#{$gl-padding} * 2)); - margin-left: auto; - margin-right: auto; - } - } -} - .issuable-details { section { .issuable-discussion { diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 237869aa544..d129eb12a45 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -98,7 +98,7 @@ } .label { - padding: 8px 9px 9px $gl-padding; + padding: 8px 9px 9px; font-size: 14px; } } diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index dd27a06fcd2..712bd3da22f 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -105,19 +105,19 @@ li { flex: 1; text-align: center; + border-left: 1px solid $border-color; &:first-of-type { + border-left: none; border-top-left-radius: $border-radius-default; } &:last-of-type { - border-left: 1px solid $border-color; border-top-right-radius: $border-radius-default; } &:not(.active) { background-color: $gray-light; - border-left: 1px solid $border-color; } a { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 41b1b47713d..1b1126695a1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -424,11 +424,19 @@ .merge-request-tabs-holder { background-color: $white-light; + .container-limited { + max-width: $limited-layout-width; + } + &.affix { top: 100px; left: 0; z-index: 10; transition: right .15s; + + @media (max-width: $screen-xs-max) { + right: 0; + } } &:not(.affix) .container-fluid { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 074abec7692..e54e12be82f 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -109,7 +109,7 @@ margin: auto; margin-top: 0; text-align: center; - font-size: 13px; + font-size: 12px; @media (max-width: $screen-sm-max) { // On smaller devices the warning becomes the fourth item in the list, diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index b512da0939f..8f15775ee03 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -557,18 +557,14 @@ ul.notes { &.is-active { color: $gl-text-green; - svg path { + svg { fill: $gl-text-green; } } svg { position: relative; - color: $gray-darkest; - - path { - fill: $gray-darkest; - } + fill: $gray-darkest; } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b1d9421aa43..dc119843b65 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -635,8 +635,8 @@ .grouped-pipeline-dropdown { padding: 0; - width: 191px; - min-width: 191px; + width: 195px; + min-width: 195px; left: auto; right: -195px; top: -4px; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index f8da0983b77..100ace41f2a 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -22,8 +22,8 @@ background: $theme-graphite; } - &.ui_gray { - background: $theme-gray; + &.ui_black { + background: $theme-black; } &.ui_green { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d6aa4c4c032..b99be02ab0c 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -627,6 +627,12 @@ pre.light-well { } } +.commits-search-form { + .input-short { + min-width: 200px; + } +} + .project-last-commit { @media (min-width: $screen-sm-min) { margin-top: $gl-padding; diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index 285e8495342..4f6a7e9e2cb 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -10,7 +10,7 @@ class Admin::DeployKeysController < Admin::ApplicationController end def create - @deploy_key = deploy_keys.new(deploy_key_params) + @deploy_key = deploy_keys.new(deploy_key_params.merge(user: current_user)) if @deploy_key.save redirect_to admin_deploy_keys_path @@ -39,6 +39,6 @@ class Admin::DeployKeysController < Admin::ApplicationController end def deploy_key_params - params.require(:deploy_key).permit(:key, :title) + params.require(:deploy_key).permit(:key, :title, :can_push) end end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 1e3d194e9f9..add1c819adf 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,17 +1,18 @@ class Admin::GroupsController < Admin::ApplicationController - before_action :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update] + before_action :group, only: [:edit, :update, :destroy, :project_update, :members_update] def index - @groups = Group.all + @groups = Group.with_statistics @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.page(params[:page]) end def show + @group = Group.with_statistics.joins(:route).group('routes.path').find_by_full_path(params[:id]) @members = @group.members.order("access_level DESC").page(params[:members_page]) @requesters = AccessRequestsFinder.new(@group).execute(current_user) - @projects = @group.projects.page(params[:projects_page]) + @projects = @group.projects.with_statistics.page(params[:projects_page]) end def new diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 1d963bdf7d5..b09ae423096 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -3,7 +3,7 @@ class Admin::ProjectsController < Admin::ApplicationController before_action :group, only: [:show, :transfer] def index - @projects = Project.all + @projects = Project.with_statistics @projects = @projects.in_namespace(params[:namespace_id]) if params[:namespace_id].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.with_push if params[:with_push].present? diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index bb912ed10cc..df9039b16b2 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -16,9 +16,6 @@ class Admin::UsersController < Admin::ApplicationController @joined_projects = user.projects.joined(@user) end - def groups - end - def keys @keys = user.keys end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 936d9bab57e..6f43ce5226d 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -82,7 +82,7 @@ module CreatesCommit return @merge_request if defined?(@merge_request) @merge_request = MergeRequestsFinder.new(current_user, project_id: @mr_target_project.id).execute.opened. - find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch) + find_by(source_branch: @mr_source_branch, target_branch: @mr_target_branch, source_project_id: @mr_source_project) end def different_project? diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index d425d0f9014..e3933e3d7b1 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -4,6 +4,9 @@ class Dashboard::TodosController < Dashboard::ApplicationController def index @sort = params[:sort] @todos = @todos.page(params[:page]) + if @todos.out_of_range? && @todos.total_pages != 0 + redirect_to url_for(params.merge(page: @todos.total_pages)) + end end def destroy diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index efe9c001bcf..b61f4e9a2db 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -42,6 +42,8 @@ class GroupsController < Groups::ApplicationController @notification_setting = current_user.notification_settings_for(group) end + @nested_groups = group.children + setup_projects respond_to do |format| @@ -75,7 +77,7 @@ class GroupsController < Groups::ApplicationController end def projects - @projects = @group.projects.page(params[:page]) + @projects = @group.projects.with_statistics.page(params[:page]) end def update diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 529e0aa2d33..b094491e006 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -16,7 +16,7 @@ class Projects::DeployKeysController < Projects::ApplicationController end def create - @key = DeployKey.new(deploy_key_params) + @key = DeployKey.new(deploy_key_params.merge(user: current_user)) set_index_vars if @key.valid? && @project.deploy_keys << @key @@ -53,6 +53,6 @@ class Projects::DeployKeysController < Projects::ApplicationController end def deploy_key_params - params.require(:deploy_key).permit(:key, :title) + params.require(:deploy_key).permit(:key, :title, :can_push) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4f66e01e0f7..2beb0df8a07 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -25,6 +25,9 @@ class Projects::IssuesController < Projects::ApplicationController def index @issues = issues_collection @issues = @issues.page(params[:page]) + if @issues.out_of_range? && @issues.total_pages != 0 + return redirect_to url_for(params.merge(page: @issues.total_pages)) + end if params[:label_name].present? @labels = LabelsFinder.new(current_user, project_id: @project.id, title: params[:label_name]).execute diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 3abebdfd032..fc8a289d49d 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -38,6 +38,9 @@ class Projects::MergeRequestsController < Projects::ApplicationController def index @merge_requests = merge_requests_collection @merge_requests = @merge_requests.page(params[:page]) + if @merge_requests.out_of_range? && @merge_requests.total_pages != 0 + return redirect_to url_for(params.merge(page: @merge_requests.total_pages)) + end if params[:label_name].present? labels_params = { project_id: @project.id, title: params[:label_name] } diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 0720be2e55d..02a97c1c574 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -26,6 +26,9 @@ class Projects::SnippetsController < Projects::ApplicationController scope: params[:scope] ) @snippets = @snippets.page(params[:page]) + if @snippets.out_of_range? && @snippets.total_pages != 0 + redirect_to namespace_project_snippets_path(page: @snippets.total_pages) + end end def new diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 953091492ae..e2d9d5ed460 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -8,7 +8,7 @@ class Projects::TagsController < Projects::ApplicationController before_action :authorize_admin_project!, only: [:destroy] def index - params[:sort] = params[:sort].presence || 'name' + params[:sort] = params[:sort].presence || sort_value_recently_updated @sort = params[:sort] @tags = TagsFinder.new(@repository, params).execute diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7445f3c113c..c6c63918fa5 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -61,7 +61,7 @@ module ProjectsHelper project_link = link_to simple_sanitize(project.name), project_path(project), { class: "project-item-select-holder" } if current_user - project_link << button_tag(type: 'button', class: "dropdown-toggle-caret js-projects-dropdown-toggle", aria: { label: "Toggle switch project dropdown" }, data: { target: ".js-dropdown-menu-projects", toggle: "dropdown" }) do + project_link << button_tag(type: 'button', class: 'dropdown-toggle-caret js-projects-dropdown-toggle', aria: { label: 'Toggle switch project dropdown' }, data: { target: '.js-dropdown-menu-projects', toggle: 'dropdown', order_by: 'last_activity_at' }) do icon("chevron-down") end end @@ -90,10 +90,12 @@ module ProjectsHelper end def project_for_deploy_key(deploy_key) - if deploy_key.projects.include?(@project) + if deploy_key.has_access_to?(@project) @project else - deploy_key.projects.find { |project| can?(current_user, :read_project, project) } + deploy_key.projects.find do |project| + can?(current_user, :read_project, project) + end end end @@ -171,48 +173,27 @@ module ProjectsHelper nav_tabs << :merge_requests end - if can?(current_user, :read_pipeline, project) - nav_tabs << :pipelines - end - - if can?(current_user, :read_build, project) - nav_tabs << :builds - end - if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project) nav_tabs << :container_registry end - if can?(current_user, :read_environment, project) - nav_tabs << :environments - end - - if can?(current_user, :admin_project, project) - nav_tabs << :settings - end - - if can?(current_user, :read_project_member, project) - nav_tabs << :team - end - - if can?(current_user, :read_issue, project) - nav_tabs << :issues - end - - if can?(current_user, :read_wiki, project) - nav_tabs << :wiki - end - - if can?(current_user, :read_project_snippet, project) - nav_tabs << :snippets - end - - if can?(current_user, :read_label, project) - nav_tabs << :labels - end + tab_ability_map = { + environments: :read_environment, + milestones: :read_milestone, + pipelines: :read_pipeline, + snippets: :read_project_snippet, + settings: :admin_project, + builds: :read_build, + labels: :read_label, + issues: :read_issue, + team: :read_project_member, + wiki: :read_wiki + } - if can?(current_user, :read_milestone, project) - nav_tabs << :milestones + tab_ability_map.each do |tab, ability| + if can?(current_user, ability, project) + nav_tabs << tab + end end nav_tabs.flatten @@ -246,11 +227,6 @@ module ProjectsHelper end end - def repository_size(project = @project) - size_in_bytes = project.repository_size * 1.megabyte - number_to_human_size(size_in_bytes, delimiter: ',', precision: 2) - end - def default_url_to_repo(project = @project) case default_clone_protocol when 'ssh' @@ -398,20 +374,6 @@ module ProjectsHelper [@project.path_with_namespace, sha, "readme"].join('-') end - def round_commit_count(project) - count = project.commit_count - - if count > 10000 - '10000+' - elsif count > 5000 - '5000+' - elsif count > 1000 - '1000+' - else - count - end - end - def current_ref @ref || @repository.try(:root_ref) end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index f03c4627050..ff787fb4131 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -11,6 +11,7 @@ module SortingHelper sort_value_due_date_soon => sort_title_due_date_soon, sort_value_due_date_later => sort_title_due_date_later, sort_value_largest_repo => sort_title_largest_repo, + sort_value_largest_group => sort_title_largest_group, sort_value_recently_signin => sort_title_recently_signin, sort_value_oldest_signin => sort_title_oldest_signin, sort_value_downvotes => sort_title_downvotes, @@ -92,6 +93,10 @@ module SortingHelper 'Largest repository' end + def sort_title_largest_group + 'Largest group' + end + def sort_title_recently_signin 'Recent sign in' end @@ -193,7 +198,11 @@ module SortingHelper end def sort_value_largest_repo - 'repository_size_desc' + 'storage_size_desc' + end + + def sort_value_largest_group + 'storage_size_desc' end def sort_value_recently_signin diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb new file mode 100644 index 00000000000..e19c67a37ca --- /dev/null +++ b/app/helpers/storage_helper.rb @@ -0,0 +1,7 @@ +module StorageHelper + def storage_counter(size_in_bytes) + precision = size_in_bytes < 1.megabyte ? 0 : 1 + + number_to_human_size(size_in_bytes, delimiter: ',', precision: precision, significant: false) + end +end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cb76cdf5981..27042798741 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -43,6 +43,8 @@ module Ci before_destroy { project } after_create :execute_hooks + after_save :update_project_statistics, if: :artifacts_size_changed? + after_destroy :update_project_statistics class << self def first_pending @@ -584,5 +586,9 @@ module Ci Ci::MaskSecret.mask!(trace, token) trace end + + def update_project_statistics + ProjectCacheWorker.perform_async(project_id, [], [:build_artifacts_size]) + end end end diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 1814469f6ce..2a97e8bae4a 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -93,11 +93,8 @@ module Ci .select("max(#{quoted_table_name}.id)") .group(:ref, :sha) - if ref - where(id: max_id, ref: ref) - else - where(id: max_id) - end + relation = ref ? where(ref: ref) : self + relation.where(id: max_id) end def self.latest_status(ref = nil) @@ -105,7 +102,7 @@ module Ci end def self.latest_successful_for(ref) - success.latest(ref).first + success.latest(ref).order(id: :desc).first end def self.truncate_sha(sha) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 0ea7b1b1098..5e63825bf99 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -92,8 +92,9 @@ module Issuable after_save :record_metrics def update_assignee_cache_counts - # make sure we flush the cache for both the old *and* new assignee - User.find(assignee_id_was).update_cache_counts if assignee_id_was + # make sure we flush the cache for both the old *and* new assignees(if they exist) + previous_assignee = User.find_by_id(assignee_id_was) if assignee_id_was + previous_assignee.update_cache_counts if previous_assignee assignee.update_cache_counts if assignee end diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 2c525d4cd7a..053f2a11aa0 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -20,4 +20,18 @@ class DeployKey < Key def destroyed_when_orphaned? self.private? end + + def has_access_to?(project) + projects.include?(project) + end + + def can_push_to?(project) + can_push? && has_access_to?(project) + end + + private + + # we don't want to notify the user for deploy keys + def notify_user + end end diff --git a/app/models/group.rb b/app/models/group.rb index ac8a82c8c1e..9888b242e98 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -48,7 +48,13 @@ class Group < Namespace end def sort(method) - order_by(method) + if method == 'storage_size_desc' + # storage_size is a virtual column so we need to + # pass a string to avoid AR adding the table name + reorder('storage_size DESC, namespaces.id DESC') + else + order_by(method) + end end def reference_prefix @@ -155,15 +161,17 @@ class Group < Namespace end def has_owner?(user) - owners.include?(user) + members_with_parents.owners.where(user_id: user).any? end def has_master?(user) - members.masters.where(user_id: user).any? + members_with_parents.masters.where(user_id: user).any? end + # Check if user is a last owner of the group. + # Parent owners are ignored for nested groups. def last_owner?(user) - has_owner?(user) && owners.size == 1 + owners.include?(user) && owners.size == 1 end def avatar_type @@ -189,6 +197,14 @@ class Group < Namespace end def refresh_members_authorized_projects - UserProjectAccessChangedService.new(users.pluck(:id)).execute + UserProjectAccessChangedService.new(users_with_parents.pluck(:id)).execute + end + + def members_with_parents + GroupMember.where(requested_at: nil, source_id: parents.map(&:id).push(id)) + end + + def users_with_parents + User.where(id: members_with_parents.select(:user_id)) end end diff --git a/app/models/key.rb b/app/models/key.rb index a5d25409730..6f377f0e8ae 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -57,10 +57,6 @@ class Key < ActiveRecord::Base ) end - def notify_user - run_after_commit { NotificationService.new.new_key(self) } - end - def post_create_hook SystemHooksService.new.execute_hooks_for(self, :create) end @@ -86,4 +82,8 @@ class Key < ActiveRecord::Base self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint end + + def notify_user + run_after_commit { NotificationService.new.new_key(self) } + end end diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index 0fd5f089db9..007eed5600a 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -5,4 +5,13 @@ class LfsObjectsProject < ActiveRecord::Base validates :lfs_object_id, presence: true validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :project_id, presence: true + + after_create :update_project_statistics + after_destroy :update_project_statistics + + private + + def update_project_statistics + ProjectCacheWorker.perform_async(project_id, [], [:lfs_objects_size]) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5ce20cc43d6..61845bf4036 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -198,7 +198,9 @@ class MergeRequest < ActiveRecord::Base end def diff_size - diffs(diff_options).size + opts = diff_options || {} + + raw_diffs(opts).size end def diff_base_commit @@ -574,11 +576,7 @@ class MergeRequest < ActiveRecord::Base ext = Gitlab::ReferenceExtractor.new(project, current_user) ext.analyze(description) - issues = ext.issues - closing_issues = Gitlab::ClosingIssueExtractor.new(project, current_user). - closed_by_message(description) - - issues - closing_issues + ext.issues - closes_issues end def target_project_path diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b52f08c7081..d41833de66f 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -9,6 +9,7 @@ class Namespace < ActiveRecord::Base cache_markdown_field :description, pipeline: :description has_many :projects, dependent: :destroy + has_many :project_statistics belongs_to :owner, class_name: "User" belongs_to :parent, class_name: "Namespace" @@ -38,6 +39,18 @@ class Namespace < ActiveRecord::Base scope :root, -> { where('type IS NULL') } + scope :with_statistics, -> do + joins('LEFT JOIN project_statistics ps ON ps.namespace_id = namespaces.id') + .group('namespaces.id') + .select( + 'namespaces.*', + 'COALESCE(SUM(ps.storage_size), 0) AS storage_size', + 'COALESCE(SUM(ps.repository_size), 0) AS repository_size', + 'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size', + 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size', + ) + end + class << self def by_path(path) find_by('lower(path) = :value', value: path.downcase) diff --git a/app/models/project.rb b/app/models/project.rb index 26fa20f856d..e0ffa7e7af7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -44,6 +44,7 @@ class Project < ActiveRecord::Base after_create :ensure_dir_exist after_create :create_project_feature, unless: :project_feature after_save :ensure_dir_exist, if: :namespace_id_changed? + after_save :update_project_statistics, if: :namespace_id_changed? # set last_activity_at to the same as created_at after_create :set_last_activity_at @@ -151,6 +152,7 @@ class Project < ActiveRecord::Base has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :project_feature, dependent: :destroy + has_one :statistics, class_name: 'ProjectStatistics', dependent: :delete has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id @@ -220,6 +222,7 @@ class Project < ActiveRecord::Base scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } + scope :with_statistics, -> { includes(:statistics) } # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { @@ -332,8 +335,10 @@ class Project < ActiveRecord::Base end def sort(method) - if method == 'repository_size_desc' - reorder(repository_size: :desc, id: :desc) + if method == 'storage_size_desc' + # storage_size is a joined column so we need to + # pass a string to avoid AR adding the table name + reorder('project_statistics.storage_size DESC, projects.id DESC') else order_by(method) end @@ -921,7 +926,7 @@ class Project < ActiveRecord::Base Rails.logger.error "Project #{old_path_with_namespace} cannot be renamed because container registry tags are present" # we currently doesn't support renaming repository if it contains tags in container registry - raise Exception.new('Project cannot be renamed, because tags are present in its container registry') + raise StandardError.new('Project cannot be renamed, because tags are present in its container registry') end if gitlab_shell.mv_repository(repository_storage_path, old_path_with_namespace, new_path_with_namespace) @@ -948,7 +953,7 @@ class Project < ActiveRecord::Base # if we cannot move namespace directory we should rollback # db changes in order to prevent out of sync between db and fs - raise Exception.new('repository cannot be renamed') + raise StandardError.new('repository cannot be renamed') end Gitlab::AppLogger.info "Project was renamed: #{old_path_with_namespace} -> #{new_path_with_namespace}" @@ -1036,14 +1041,6 @@ class Project < ActiveRecord::Base forked? && project == forked_from_project end - def update_repository_size - update_attribute(:repository_size, repository.size) - end - - def update_commit_count - update_attribute(:commit_count, repository.commit_count) - end - def forks_count forks.count end @@ -1322,4 +1319,9 @@ class Project < ActiveRecord::Base def full_path_changed? path_changed? || namespace_id_changed? end + + def update_project_statistics + stats = statistics || build_statistics + stats.update(namespace_id: namespace_id) + end end diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb new file mode 100644 index 00000000000..2270ac75071 --- /dev/null +++ b/app/models/project_statistics.rb @@ -0,0 +1,43 @@ +class ProjectStatistics < ActiveRecord::Base + belongs_to :project + belongs_to :namespace + + before_save :update_storage_size + + STORAGE_COLUMNS = [:repository_size, :lfs_objects_size, :build_artifacts_size] + STATISTICS_COLUMNS = [:commit_count] + STORAGE_COLUMNS + + def total_repository_size + repository_size + lfs_objects_size + end + + def refresh!(only: nil) + STATISTICS_COLUMNS.each do |column, generator| + if only.blank? || only.include?(column) + public_send("update_#{column}") + end + end + + save! + end + + def update_commit_count + self.commit_count = project.repository.commit_count + end + + def update_repository_size + self.repository_size = project.repository.size + end + + def update_lfs_objects_size + self.lfs_objects_size = project.lfs_objects.sum(:size) + end + + def update_build_artifacts_size + self.build_artifacts_size = project.builds.sum(:artifacts_size) + end + + def update_storage_size + self.storage_size = STORAGE_COLUMNS.sum(&method(:read_attribute)) + end +end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index 6f943feb2a7..0be6e113655 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -4,7 +4,7 @@ class GroupPolicy < BasePolicy return unless @user globally_viewable = @subject.public? || (@subject.internal? && !@user.external?) - member = @subject.users.include?(@user) + member = @subject.users_with_parents.include?(@user) owner = @user.admin? || @subject.has_owner?(@user) master = owner || @subject.has_master?(@user) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index b5db9c12622..71ef8901932 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -171,9 +171,7 @@ class ProjectPolicy < BasePolicy def disabled_features! repository_enabled = project.feature_available?(:repository, user) - unless project.feature_available?(:issues, user) - cannot!(*named_abilities(:issue)) - end + block_issues_abilities unless project.feature_available?(:merge_requests, user) && repository_enabled cannot!(*named_abilities(:merge_request)) @@ -245,11 +243,20 @@ class ProjectPolicy < BasePolicy def project_group_member?(user) project.group && ( - project.group.members.exists?(user_id: user.id) || + project.group.members_with_parents.exists?(user_id: user.id) || project.group.requesters.exists?(user_id: user.id) ) end + def block_issues_abilities + unless project.feature_available?(:issues, user) + cannot! :read_issue if project.default_issues_tracker? + cannot! :create_issue + cannot! :update_issue + cannot! :admin_issue + end + end + def named_abilities(name) [ :"read_#{name}", diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 6bbc3a9d9ff..dbe2fda27b5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -77,7 +77,7 @@ class GitPushService < BaseService types = [] end - ProjectCacheWorker.perform_async(@project.id, types) + ProjectCacheWorker.perform_async(@project.id, types, [:commit_count, :repository_size]) end # Schedules processing of commit messages. diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 20a4445bddf..96432837481 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -12,7 +12,7 @@ class GitTagPushService < BaseService project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks) Ci::CreatePipelineService.new(project, current_user, @push_data).execute - ProjectCacheWorker.perform_async(project.id) + ProjectCacheWorker.perform_async(project.id, [], [:commit_count, :repository_size]) true end diff --git a/app/services/groups/create_service.rb b/app/services/groups/create_service.rb index 2bccd584dde..febeb661fb5 100644 --- a/app/services/groups/create_service.rb +++ b/app/services/groups/create_service.rb @@ -12,6 +12,13 @@ module Groups return @group end + if @group.parent && !can?(current_user, :admin_group, @group.parent) + @group.parent = nil + @group.errors.add(:parent_id, 'manage access required to create subgroup') + + return @group + end + @group.name ||= @group.path.dup @group.save @group.add_owner(current_user) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index ab3d2a9a0cd..4ce5fd993d9 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -36,14 +36,10 @@ class IssuableBaseService < BaseService end end - def filter_params(issuable_ability_name = :issue) - filter_assignee - filter_milestone - filter_labels + def filter_params(issuable) + ability_name = :"admin_#{issuable.to_ability_name}" - ability = :"admin_#{issuable_ability_name}" - - unless can?(current_user, ability, project) + unless can?(current_user, ability_name, project) params.delete(:milestone_id) params.delete(:labels) params.delete(:add_label_ids) @@ -52,14 +48,35 @@ class IssuableBaseService < BaseService params.delete(:assignee_id) params.delete(:due_date) end + + filter_assignee(issuable) + filter_milestone + filter_labels end - def filter_assignee - if params[:assignee_id] == IssuableFinder::NONE - params[:assignee_id] = '' + def filter_assignee(issuable) + return unless params[:assignee_id].present? + + assignee_id = params[:assignee_id] + + if assignee_id.to_s == IssuableFinder::NONE + params[:assignee_id] = "" + else + params.delete(:assignee_id) unless assignee_can_read?(issuable, assignee_id) end end + def assignee_can_read?(issuable, assignee_id) + new_assignee = User.find_by_id(assignee_id) + + return false unless new_assignee.present? + + ability_name = :"read_#{issuable.to_ability_name}" + resource = issuable.persisted? ? issuable : project + + can?(new_assignee, ability_name, resource) + end + def filter_milestone milestone_id = params[:milestone_id] return unless milestone_id @@ -138,7 +155,7 @@ class IssuableBaseService < BaseService def create(issuable) merge_slash_commands_into_params!(issuable) - filter_params + filter_params(issuable) params.delete(:state_event) params[:author] ||= current_user @@ -180,7 +197,7 @@ class IssuableBaseService < BaseService change_state(issuable) change_subscription(issuable) change_todo(issuable) - filter_params + filter_params(issuable) old_labels = issuable.labels.to_a old_mentioned_users = issuable.mentioned_users.to_a diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 742e834df97..35af867a098 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -17,10 +17,6 @@ module Issues private - def filter_params - super(:issue) - end - def execute_hooks(issue, action = 'open') issue_data = hook_data(issue, action) hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 800fd39c424..70e25956dc7 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -38,10 +38,6 @@ module MergeRequests private - def filter_params - super(:merge_request) - end - def merge_requests_for(branch) origin_merge_requests = @project.origin_merge_requests .opened.where(source_branch: branch).to_a diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index d75592e31f3..1beca9f4109 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -41,7 +41,7 @@ module Notes # We must add the error after we call #save because errors are reset # when #save is called if only_commands - note.errors.add(:commands_only, 'Your commands have been executed!') + note.errors.add(:commands_only, 'Commands applied') end note.commands_changes = command_params.keys diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb index 7d38ac3a374..8559908e0c3 100644 --- a/app/services/users/refresh_authorized_projects_service.rb +++ b/app/services/users/refresh_authorized_projects_service.rb @@ -74,7 +74,7 @@ module Users # remove - The IDs of the authorization rows to remove. # add - Rows to insert in the form `[user id, project id, access level]` def update_authorizations(remove = [], add = []) - return if remove.empty? && add.empty? + return if remove.empty? && add.empty? && user.authorized_projects_populated User.transaction do user.remove_project_authorizations(remove) unless remove.empty? diff --git a/app/views/abuse_report_mailer/notify.html.haml b/app/views/abuse_report_mailer/notify.html.haml index 2741eb44357..d50b4071745 100644 --- a/app/views/abuse_report_mailer/notify.html.haml +++ b/app/views/abuse_report_mailer/notify.html.haml @@ -1,7 +1,7 @@ %p - #{link_to @abuse_report.user.name, user_url(@abuse_report.user)} - (@#{@abuse_report.user.username}) was reported for abuse by - #{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)} + #{link_to @abuse_report.user.name, user_url(@abuse_report.user)} + (@#{@abuse_report.user.username}) was reported for abuse by + #{link_to @abuse_report.reporter.name, user_url(@abuse_report.reporter)} (@#{@abuse_report.reporter.username}). %blockquote diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 7accd2529af..4612a7a058a 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -321,7 +321,7 @@ = f.text_field :recaptcha_site_key, class: 'form-control' .help-block Generate site and private keys at - %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha + %a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha .form-group = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' @@ -342,7 +342,7 @@ = f.text_field :akismet_api_key, class: 'form-control' .help-block Generate API key at - %a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com + %a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com %fieldset %legend Abuse reports diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml index 042971e1eed..82781f6716d 100644 --- a/app/views/admin/applications/_delete_form.html.haml +++ b/app/views/admin/applications/_delete_form.html.haml @@ -1,4 +1,4 @@ - submit_btn_css ||= 'btn btn-link btn-remove btn-sm' = form_tag admin_application_path(application) do - %input{:name => "_method", :type => "hidden", :value => "delete"}/ + %input{ :name => "_method", :type => "hidden", :value => "delete" }/ = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml index f8cd98f0ec4..b3a3b4c1d45 100644 --- a/app/views/admin/applications/index.html.haml +++ b/app/views/admin/applications/index.html.haml @@ -15,7 +15,7 @@ %th %tbody.oauth-applications - @applications.each do |application| - %tr{:id => "application_#{application.id}"} + %tr{ :id => "application_#{application.id}" } %td= link_to application.name, admin_application_path(application) %td= application.redirect_uri %td= application.access_tokens.map(&:resource_owner_id).uniq.count diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index 05855db963a..4f982a6e369 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -43,4 +43,4 @@ .panel.panel-default - %iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} + %iframe{ src: sidekiq_path, width: '100%', height: 970, style: "border: none" } diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index c05538a393c..4f2ae081d7a 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -10,7 +10,7 @@ %br.clearfix --if @broadcast_messages.any? +- if @broadcast_messages.any? %table.table %thead %tr diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 149593e7f46..7b71bb5b287 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -1,27 +1,34 @@ - page_title "Deploy Keys" -.panel.panel-default.prepend-top-default - .panel-heading - Public deploy keys (#{@deploy_keys.count}) - .controls - = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm" - - if @deploy_keys.any? - .table-holder - %table.table - %thead.panel-heading + +%h3.page-title.deploy-keys-title + Public deploy keys (#{@deploy_keys.count}) + .pull-right + = link_to 'New Deploy Key', new_admin_deploy_key_path, class: 'btn btn-new btn-sm btn-inverted' + +- if @deploy_keys.any? + .table-holder.deploy-keys-list + %table.table + %thead + %tr + %th.col-sm-2 Title + %th.col-sm-4 Fingerprint + %th.col-sm-2 Write access allowed + %th.col-sm-2 Added at + %th.col-sm-2 + %tbody + - @deploy_keys.each do |deploy_key| %tr - %th Title - %th Fingerprint - %th Added at - %th - %tbody - - @deploy_keys.each do |deploy_key| - %tr - %td - %strong= deploy_key.title - %td - %code.key-fingerprint= deploy_key.fingerprint - %td - %span.cgray - added #{time_ago_with_tooltip(deploy_key.created_at)} - %td - = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" + %td + %strong= deploy_key.title + %td + %code.key-fingerprint= deploy_key.fingerprint + %td + - if deploy_key.can_push? + Yes + - else + No + %td + %span.cgray + added #{time_ago_with_tooltip(deploy_key.created_at)} + %td + = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key pull-right' diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml index 5c410a695bf..a064efc231f 100644 --- a/app/views/admin/deploy_keys/new.html.haml +++ b/app/views/admin/deploy_keys/new.html.haml @@ -16,6 +16,14 @@ Paste a machine public key here. Read more about how to generate it = link_to "here", help_page_path("ssh/README") = f.text_area :key, class: "form-control thin_area", rows: 5 + .form-group + .control-label + .col-sm-10 + = f.label :can_push do + = f.check_box :can_push + %strong Write access allowed + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) .form-actions = f.submit 'Create', class: "btn-create btn" diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml index cf28f92853e..e3a77dfdf10 100644 --- a/app/views/admin/groups/_group.html.haml +++ b/app/views/admin/groups/_group.html.haml @@ -5,6 +5,9 @@ = link_to 'Edit', admin_group_edit_path(group), id: "edit_#{dom_id(group)}", class: 'btn' = link_to 'Delete', [:admin, group], data: { confirm: "Are you sure you want to remove #{group.name}?" }, method: :delete, class: 'btn btn-remove' .stats + %span.badge + = storage_counter(group.storage_size) + %span = icon('bookmark') = number_with_delimiter(group.projects.count) @@ -13,7 +16,7 @@ = icon('users') = number_with_delimiter(group.users.count) - %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} + %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) } = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 794f910a61f..07775247cfd 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -27,6 +27,8 @@ = sort_title_recently_updated = link_to admin_groups_path(sort: sort_value_oldest_updated, name: project_name) do = sort_title_oldest_updated + = link_to admin_groups_path(sort: sort_value_largest_group, name: project_name) do + = sort_title_largest_group = link_to new_admin_group_path, class: "btn btn-new" do New Group %ul.content-list diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 7b0175af214..30b3fabdd7e 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -39,6 +39,18 @@ = @group.created_at.to_s(:medium) %li + %span.light Storage: + %strong= storage_counter(@group.storage_size) + ( + = storage_counter(@group.repository_size) + repositories, + = storage_counter(@group.build_artifacts_size) + build artifacts, + = storage_counter(@group.lfs_objects_size) + LFS + ) + + %li %span.light Group Git LFS status: %strong = group_lfs_status(@group) @@ -55,8 +67,8 @@ %li %strong = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - %span.label.label-gray - = repository_size(project) + %span.badge + = storage_counter(project.statistics.storage_size) %span.pull-right.light %span.monospace= project.path_with_namespace + ".git" .panel-footer @@ -73,8 +85,8 @@ %li %strong = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - %span.label.label-gray - = repository_size(project) + %span.badge + = storage_counter(project.statistics.storage_size) %span.pull-right.light %span.monospace= project.path_with_namespace + ".git" @@ -91,7 +103,7 @@ = form_tag admin_group_members_update_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div = users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all) - %div.prepend-top-10 + .prepend-top-10 = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr = button_tag 'Add users to group', class: "btn btn-create" diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index c217490963f..551edf14361 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -29,7 +29,7 @@ System hook will be triggered on set of events like creating project or adding ssh key. But you can also enable extra triggers like Push events. - %div.prepend-top-default + .prepend-top-default = f.check_box :push_events, class: 'pull-left' .prepend-left-20 = f.label :push_events, class: 'list-label' do @@ -54,7 +54,7 @@ = f.submit "Add System Hook", class: "btn btn-create" %hr --if @hooks.any? +- if @hooks.any? .panel.panel-default .panel-heading System hooks (#{@hooks.count}) @@ -70,4 +70,3 @@ - if hook.send(trigger) %span.label.label-gray= trigger.titleize %span.label.label-gray SSL Verification: #{hook.enable_ssl_verification ? "enabled" : "disabled"} - diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml index be224d66855..77b174fbb27 100644 --- a/app/views/admin/labels/_label.html.haml +++ b/app/views/admin/labels/_label.html.haml @@ -1,4 +1,4 @@ -%li{id: dom_id(label)} +%li{ id: dom_id(label) } .label-row = render_colored_label(label, tooltip: false) = markdown_field(label, :description) diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 8bc7dc7dd51..2e6f03fcde0 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -69,8 +69,8 @@ .controls - if project.archived %span.label.label-warning archived - %span.label.label-gray - = repository_size(project) + %span.badge + = storage_counter(project.statistics.storage_size) = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn" = link_to 'Delete', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-remove" .title diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 6c7c3c48604..2967da6e692 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -65,9 +65,16 @@ = @project.repository.path_to_repo %li - %span.light Size - %strong - = repository_size(@project) + %span.light Storage: + %strong= storage_counter(@project.statistics.storage_size) + ( + = storage_counter(@project.statistics.repository_size) + repository, + = storage_counter(@project.statistics.build_artifacts_size) + build artifacts, + = storage_counter(@project.statistics.lfs_objects_size) + LFS + ) %li %span.light last commit: diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 64893b38c58..975bd950ae1 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -1,4 +1,4 @@ -%tr{id: dom_id(runner)} +%tr{ id: dom_id(runner) } %td - if runner.shared? %span.label.label-success shared diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index ce5e21e54cc..9984e733956 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -15,10 +15,8 @@ %ul.nav-links = nav_link(path: 'users#show') do = link_to "Account", admin_user_path(@user) - = nav_link(path: 'users#groups') do - = link_to "Groups", groups_admin_user_path(@user) = nav_link(path: 'users#projects') do - = link_to "Projects", projects_admin_user_path(@user) + = link_to "Groups and projects", projects_admin_user_path(@user) = nav_link(path: 'users#keys') do = link_to "SSH keys", keys_admin_user_path(@user) = nav_link(controller: :identities) do diff --git a/app/views/admin/users/_user.html.haml b/app/views/admin/users/_user.html.haml index 2d9588f9d27..3b5c713ac2d 100644 --- a/app/views/admin/users/_user.html.haml +++ b/app/views/admin/users/_user.html.haml @@ -18,7 +18,7 @@ = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: 'btn' - unless user == current_user .dropdown.inline - %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', data: { toggle: 'dropdown' } } + %a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', data: { toggle: 'dropdown' } } = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/admin/users/groups.html.haml b/app/views/admin/users/groups.html.haml deleted file mode 100644 index 8f6d13b881a..00000000000 --- a/app/views/admin/users/groups.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -- page_title "Groups", @user.name, "Users" -= render 'admin/users/head' - -- group_members = @user.group_members.includes(:source) -- if group_members.any? - .panel.panel-default - .panel-heading Groups: - %ul.well-list - - group_members.each do |group_member| - - group = group_member.group - %li.group_member - %span{class: ("list-item-name" unless group_member.owner?)} - %strong= link_to group.name, admin_group_path(group) - .pull-right - %span.light= group_member.human_access - - unless group_member.owner? - = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - %i.fa.fa-times.fa-inverse -- else - .nothing-here-block This user has no groups. diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index dd6b7303493..15eaf1c0e67 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -1,15 +1,21 @@ -- page_title "Projects", @user.name, "Users" +- page_title "Groups and projects", @user.name, "Users" = render 'admin/users/head' - if @user.groups.any? .panel.panel-default .panel-heading Group projects %ul.well-list - - @user.groups.each do |group| - %li + - @user.group_members.includes(:source).each do |group_member| + - group = group_member.group + %li.group_member %strong= link_to group.name, admin_group_path(group) – access to #{pluralize(group.projects.count, 'project')} + .pull-right + %span.light.vertical-align-middle= group_member.human_access + - unless group_member.owner? + = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from group' do + %i.fa.fa-times.fa-inverse .row .col-md-6 @@ -35,8 +41,8 @@ - if member.owner? %span.light Owner - else - %span.light= member.human_access + %span.light.vertical-align-middle= member.human_access - if member.respond_to? :project - = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do + = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_member_message(member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove prepend-left-10", title: 'Remove user from project' do %i.fa.fa-times diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 76c9ed0ee8b..a71240986c9 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -40,7 +40,7 @@ %li.two-factor-status %span.light Two-factor Authentication: - %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} + %strong{ class: @user.two_factor_enabled? ? 'cgreen' : 'cred' } - if @user.two_factor_enabled? Enabled = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication' diff --git a/app/views/ci/lints/_create.html.haml b/app/views/ci/lints/_create.html.haml index 61c7cce20b2..c91602fcff7 100644 --- a/app/views/ci/lints/_create.html.haml +++ b/app/views/ci/lints/_create.html.haml @@ -36,7 +36,7 @@ - if build[:allow_failure] %b Allowed to fail --else +- else %p %b Status: syntax is incorrect diff --git a/app/views/dashboard/projects/_zero_authorized_projects.html.haml b/app/views/dashboard/projects/_zero_authorized_projects.html.haml index 4a55aac0df6..1bbd4602ecf 100644 --- a/app/views/dashboard/projects/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/projects/_zero_authorized_projects.html.haml @@ -33,7 +33,7 @@ = link_to new_project_path, class: "btn btn-new" do New project --if publicish_project_count > 0 +- if publicish_project_count > 0 .blank-state .blank-state-icon = icon("globe") diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index cc077fad32a..9849b31d7e2 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -1,4 +1,4 @@ -%li{class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data:{url: todo_target_path(todo)} } +%li{ class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data: { url: todo_target_path(todo) } } = author_avatar(todo, size: 40) .todo-item.todo-block diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index e13f404fee2..f4efcfb27b2 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -5,14 +5,14 @@ .top-area %ul.nav-links - todo_pending_active = ('active' if params[:state].blank? || params[:state] == 'pending') - %li{class: "todos-pending #{todo_pending_active}"}> + %li{ class: "todos-pending #{todo_pending_active}" }> = link_to todos_filter_path(state: 'pending') do %span To do %span.badge = number_with_delimiter(todos_pending_count) - todo_done_active = ('active' if params[:state] == 'done') - %li{class: "todos-done #{todo_done_active}"}> + %li{ class: "todos-done #{todo_done_active}" }> = link_to todos_filter_path(state: 'done') do %span Done @@ -50,7 +50,7 @@ data: { data: todo_actions_options, default_label: 'Action' } }) .pull-right .dropdown.inline.prepend-left-10 - %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light - if @sort.present? = sort_options_hash[@sort] diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 84e13693dfd..5d359538efe 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,16 +1,16 @@ = form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f| - %div.form-group + .form-group = f.label "Username or email", for: :login = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required." - %div.form-group + .form-group = f.label :password = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required." - if devise_mapping.rememberable? .remember-me.checkbox - %label{for: "user_remember_me"} + %label{ for: "user_remember_me" } = f.check_box :remember_me %span Remember me .pull-right.forgot-password = link_to "Forgot your password?", new_password_path(resource_name) - %div.submit-container.move-submit-down + .submit-container.move-submit-down = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index a6cadbcbdff..2556cb6f59b 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,13 +1,13 @@ = form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user', class: 'gl-show-field-errors') do .form-group = label_tag :username, 'Username or email' - = text_field_tag :username, nil, {class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } + = text_field_tag :username, nil, { class: "form-control top", title: "This field is required", autofocus: "autofocus", required: true } .form-group = label_tag :password = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox - %label{for: "remember_me"} + %label{ for: "remember_me" } = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me = submit_tag "Sign in", class: "btn-save btn" diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 3ab5461f929..3159d21598a 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,13 +1,13 @@ = form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do .form-group = label_tag :username, "#{server['label']} Username" - = text_field_tag :username, nil, {class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } + = text_field_tag :username, nil, { class: "form-control top", title: "This field is required.", autofocus: "autofocus", required: true } .form-group = label_tag :password = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", required: true } - if devise_mapping.rememberable? .remember-me.checkbox - %label{for: "remember_me"} + %label{ for: "remember_me" } = check_box_tag :remember_me, '1', false, id: 'remember_me' %span Remember me = submit_tag "Sign in", class: "btn-save btn" diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index 2cadc424668..951f03083bf 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -7,7 +7,7 @@ .login-box .login-body - if @user.two_factor_otp_enabled? - = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: 'edit_user gl-show-field-errors' }) do |f| + = form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_u2f_enabled?}" }) do |f| - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) %div diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 8908b64cdac..e87a16a5157 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -1,4 +1,4 @@ -%div.omniauth-container +.omniauth-container %p %span.light Sign in with diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 86edaf14e43..eddfce363a7 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -1,18 +1,18 @@ - if form_based_providers.any? - if crowd_enabled? - .login-box.tab-pane.active{id: "crowd", role: 'tabpanel', class: 'tab-pane'} + .login-box.tab-pane.active{ id: "crowd", role: 'tabpanel' } .login-body = render 'devise/sessions/new_crowd' - @ldap_servers.each_with_index do |server, i| - .login-box.tab-pane{id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?)} + .login-box.tab-pane{ id: "#{server['provider_name']}", role: 'tabpanel', class: (:active if i.zero? && !crowd_enabled?) } .login-body = render 'devise/sessions/new_ldap', server: server - if signin_enabled? - .login-box.tab-pane{id: 'ldap-standard', role: 'tabpanel'} + .login-box.tab-pane{ id: 'ldap-standard', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' - elsif signin_enabled? - .login-box.tab-pane.active{id: 'login-pane', role: 'tabpanel'} + .login-box.tab-pane.active{ id: 'login-pane', role: 'tabpanel' } .login-body = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml index 3133f6de2e8..545a938f4be 100644 --- a/app/views/devise/shared/_signup_box.html.haml +++ b/app/views/devise/shared/_signup_box.html.haml @@ -1,18 +1,18 @@ -#register-pane.login-box{ role: 'tabpanel', class: 'tab-pane' } +#register-pane.tab-pane.login-box{ role: 'tabpanel' } .login-body = form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f| .devise-errors = devise_error_messages! - %div.form-group + .form-group = f.label :name = f.text_field :name, class: "form-control top", required: true, title: "This field is required." - %div.username.form-group + .username.form-group = f.label :username = f.text_field :username, class: "form-control middle", pattern: Gitlab::Regex::NAMESPACE_REGEX_STR_SIMPLE, required: true, title: 'Please create a username with only alphanumeric characters.' %p.validation-error.hide Username is already taken. %p.validation-success.hide Username is available. %p.validation-pending.hide Checking username availability... - %div.form-group + .form-group = f.label :email = f.email_field :email, class: "form-control middle", required: true, title: "Please provide a valid email address." .form-group.append-bottom-20#password-strength diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index aec1b31ce62..8c4ad30c832 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -3,7 +3,7 @@ %li.active = link_to "Crowd", "#crowd", 'data-toggle' => 'tab' - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} + %li{ class: (:active if i.zero? && !crowd_enabled?) } = link_to server['label'], "##{server['provider_name']}", 'data-toggle' => 'tab' - if signin_enabled? %li diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml index 05246303fb6..c225d800a98 100644 --- a/app/views/devise/shared/_tabs_normal.html.haml +++ b/app/views/devise/shared/_tabs_normal.html.haml @@ -1,6 +1,6 @@ -%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist'} +%ul.nav-links.new-session-tabs.nav-tabs{ role: 'tablist' } %li.active{ role: 'presentation' } - %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab'} Sign in + %a{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in - if signin_enabled? && signup_enabled? - %li{ role: 'presentation'} - %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab'} Register + %li{ role: 'presentation' } + %a{ href: '#register-pane', data: { toggle: 'tab' }, role: 'tab' } Register diff --git a/app/views/discussions/_diff_discussion.html.haml b/app/views/discussions/_diff_discussion.html.haml index 1411daeb4a6..2deadbeeceb 100644 --- a/app/views/discussions/_diff_discussion.html.haml +++ b/app/views/discussions/_diff_discussion.html.haml @@ -1,5 +1,5 @@ - expanded = local_assigns.fetch(:expanded, true) -%tr.notes_holder{class: ('hide' unless expanded)} +%tr.notes_holder{ class: ('hide' unless expanded) } %td.notes_line{ colspan: 2 } %td.notes_content .content diff --git a/app/views/discussions/_jump_to_next.html.haml b/app/views/discussions/_jump_to_next.html.haml index 7ed09dd1a98..69bd416c4de 100644 --- a/app/views/discussions/_jump_to_next.html.haml +++ b/app/views/discussions/_jump_to_next.html.haml @@ -5,5 +5,5 @@ %button.btn.btn-default.discussion-next-btn.has-tooltip{ "@click" => "jumpToNextUnresolvedDiscussion", title: "Jump to next unresolved discussion", "aria-label" => "Jump to next unresolved discussion", - data: { container: "body" }} + data: { container: "body" } } = custom_icon("next_discussion") diff --git a/app/views/discussions/_parallel_diff_discussion.html.haml b/app/views/discussions/_parallel_diff_discussion.html.haml index f1072ce0feb..ef16b516e2c 100644 --- a/app/views/discussions/_parallel_diff_discussion.html.haml +++ b/app/views/discussions/_parallel_diff_discussion.html.haml @@ -1,9 +1,9 @@ - expanded = discussion_left.try(:expanded?) || discussion_right.try(:expanded?) -%tr.notes_holder{class: ('hide' unless expanded)} +%tr.notes_holder{ class: ('hide' unless expanded) } - if discussion_left %td.notes_line.old %td.notes_content.parallel.old - .content{class: ('hide' unless discussion_left.expanded?)} + .content{ class: ('hide' unless discussion_left.expanded?) } = render "discussions/notes", discussion: discussion_left, line_type: 'old' - else %td.notes_line.old= "" @@ -13,7 +13,7 @@ - if discussion_right %td.notes_line.new %td.notes_content.parallel.new - .content{class: ('hide' unless discussion_right.expanded?)} + .content{ class: ('hide' unless discussion_right.expanded?) } = render "discussions/notes", discussion: discussion_right, line_type: 'new' - else %td.notes_line.new= "" diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml index 001a711b1dd..84b4ce5b606 100644 --- a/app/views/doorkeeper/applications/_delete_form.html.haml +++ b/app/views/doorkeeper/applications/_delete_form.html.haml @@ -1,6 +1,6 @@ - submit_btn_css ||= 'btn btn-link btn-remove btn-sm' = form_tag oauth_application_path(application) do - %input{:name => "_method", :type => "hidden", :value => "delete"}/ + %input{ :name => "_method", :type => "hidden", :value => "delete" }/ - if defined? small = button_tag type: "submit", class: "btn btn-transparent", data: { confirm: "Are you sure?" } do %span.sr-only diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml index 3998e66f40d..aa271150b07 100644 --- a/app/views/doorkeeper/applications/index.html.haml +++ b/app/views/doorkeeper/applications/index.html.haml @@ -31,7 +31,7 @@ %th.last-heading %tbody - @applications.each do |application| - %tr{id: "application_#{application.id}"} + %tr{ id: "application_#{application.id}" } %td= link_to application.name, oauth_application_path(application) %td - application.redirect_uri.split.each do |uri| @@ -63,7 +63,7 @@ %tbody - @authorized_apps.each do |app| - token = app.authorized_tokens.order('created_at desc').first - %tr{id: "application_#{app.id}"} + %tr{ id: "application_#{app.id}" } %td= app.name %td= token.created_at %td= token.scopes @@ -72,7 +72,7 @@ %tr %td Anonymous - %div.help-block + .help-block %em Authorization was granted by entering your username and password in the application. %td= token.created_at %td= token.scopes diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml index a4c607cea60..6117b00149f 100644 --- a/app/views/doorkeeper/authorizations/error.html.haml +++ b/app/views/doorkeeper/authorizations/error.html.haml @@ -1,3 +1,3 @@ %h3.page-title An error has occurred -%main{:role => "main"} +%main{ :role => "main" } %pre= @pre_auth.error_response.body[:error_description] diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index ce050007204..2a0e301c8dd 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -1,5 +1,5 @@ %h3.page-title Authorization required -%main{:role => "main"} +%main{ :role => "main" } %p.h4 Authorize %strong.text-info= @pre_auth.client.name diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml index 01f9e46f142..44e868e6782 100644 --- a/app/views/doorkeeper/authorizations/show.html.haml +++ b/app/views/doorkeeper/authorizations/show.html.haml @@ -1,3 +1,3 @@ %h3.page-title Authorization code: -%main{:role => "main"} +%main{ :role => "main" } %code#authorization_code= params[:code] diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml index 9f02a8d2ed9..11c1e67878e 100644 --- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -3,7 +3,7 @@ - path = oauth_authorized_application_path(0, token_id: token) - else - path = oauth_authorized_application_path(application) - + = form_tag path do - %input{:name => "_method", :type => "hidden", :value => "delete"}/ + %input{ :name => "_method", :type => "hidden", :value => "delete" }/ = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-remove btn-sm' diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml index b184b9c01d4..c8a585560a2 100644 --- a/app/views/doorkeeper/authorized_applications/index.html.haml +++ b/app/views/doorkeeper/authorized_applications/index.html.haml @@ -1,6 +1,6 @@ %header.page-header %h1 Your authorized applications -%main{:role => "main"} +%main{ :role => "main" } .table-holder %table.table.table-striped %thead diff --git a/app/views/emojis/index.html.haml b/app/views/emojis/index.html.haml index 790d90ad3ee..49bd9acd2db 100644 --- a/app/views/emojis/index.html.haml +++ b/app/views/emojis/index.html.haml @@ -7,5 +7,5 @@ %ul.clearfix.emoji-menu-list - emojis.each do |emoji| %li.pull-left.text-center.emoji-menu-list-item - %button.emoji-menu-btn.text-center.js-emoji-btn{type: "button"} + %button.emoji-menu-btn.text-center.js-emoji-btn{ type: "button" } = emoji_icon(emoji["name"], emoji["unicode"], emoji["aliases"]) diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml index 8bddbef3562..a97cbd4d4b3 100644 --- a/app/views/errors/access_denied.html.haml +++ b/app/views/errors/access_denied.html.haml @@ -1,6 +1,5 @@ - content_for(:title, 'Access Denied') -%img{:alt => "GitLab Logo", - :src => image_path('logo.svg')} +%img{ :alt => "GitLab Logo", :src => image_path('logo.svg') } %h1 403 .container diff --git a/app/views/errors/encoding.html.haml b/app/views/errors/encoding.html.haml index 064ff14ad2c..64f7f8e0836 100644 --- a/app/views/errors/encoding.html.haml +++ b/app/views/errors/encoding.html.haml @@ -1,6 +1,5 @@ - content_for(:title, 'Encoding Error') -%img{:alt => "GitLab Logo", - :src => image_path('logo.svg')} +%img{ :alt => "GitLab Logo", :src => image_path('logo.svg') } %h1 500 .container diff --git a/app/views/errors/git_not_found.html.haml b/app/views/errors/git_not_found.html.haml index c5c12a410ac..d860957665b 100644 --- a/app/views/errors/git_not_found.html.haml +++ b/app/views/errors/git_not_found.html.haml @@ -1,6 +1,5 @@ - content_for(:title, 'Git Resource Not Found') -%img{:alt => "GitLab Logo", - :src => image_path('logo.svg')} +%img{ :alt => "GitLab Logo", :src => image_path('logo.svg') } %h1 404 .container diff --git a/app/views/errors/not_found.html.haml b/app/views/errors/not_found.html.haml index 50a54a93cb5..a0b9a632e22 100644 --- a/app/views/errors/not_found.html.haml +++ b/app/views/errors/not_found.html.haml @@ -1,6 +1,5 @@ - content_for(:title, 'Not Found') -%img{:alt => "GitLab Logo", - :src => image_path('logo.svg')} +%img{ :alt => "GitLab Logo", :src => image_path('logo.svg') } %h1 404 .container diff --git a/app/views/errors/omniauth_error.html.haml b/app/views/errors/omniauth_error.html.haml index d91f1878cb6..72508b91134 100644 --- a/app/views/errors/omniauth_error.html.haml +++ b/app/views/errors/omniauth_error.html.haml @@ -1,6 +1,5 @@ - content_for(:title, 'Auth Error') -%img{:alt => "GitLab Logo", - :src => image_path('logo.svg')} +%img{ :alt => "GitLab Logo", :src => image_path('logo.svg') } %h1 422 .container diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml index 083c3936212..51585314a62 100644 --- a/app/views/events/_event_issue.atom.haml +++ b/app/views/events/_event_issue.atom.haml @@ -1,2 +1,2 @@ -%div{xmlns: "http://www.w3.org/1999/xhtml"} +%div{ xmlns: "http://www.w3.org/1999/xhtml" } = markdown(issue.description, pipeline: :atom, project: issue.project, author: issue.author) diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml index d7e05600627..56fc8b86217 100644 --- a/app/views/events/_event_merge_request.atom.haml +++ b/app/views/events/_event_merge_request.atom.haml @@ -1,2 +1,2 @@ -%div{xmlns: "http://www.w3.org/1999/xhtml"} +%div{ xmlns: "http://www.w3.org/1999/xhtml" } = markdown(merge_request.description, pipeline: :atom, project: merge_request.project, author: merge_request.author) diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml index 1154f982821..6fa2f9bd4db 100644 --- a/app/views/events/_event_note.atom.haml +++ b/app/views/events/_event_note.atom.haml @@ -1,2 +1,2 @@ -%div{xmlns: "http://www.w3.org/1999/xhtml"} +%div{ xmlns: "http://www.w3.org/1999/xhtml" } = markdown(note.note, pipeline: :atom, project: note.project, author: note.author) diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 28bee1d0a33..f8f0bcb7608 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -1,4 +1,4 @@ -%div{xmlns: "http://www.w3.org/1999/xhtml"} +%div{ xmlns: "http://www.w3.org/1999/xhtml" } - event.commits.first(15).each do |commit| %p %strong= commit[:author][:name] diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index bba6e0d2c20..2fb6b5647da 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span{class: event.action_name} + %span{ class: event.action_name } - if event.target = event.action_name %strong diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml index aba64dd17d0..80cf2344fe1 100644 --- a/app/views/events/event/_created_project.html.haml +++ b/app/views/events/event/_created_project.html.haml @@ -1,6 +1,6 @@ .event-title %span.author_name= link_to_author event - %span{class: event.action_name} + %span{ class: event.action_name } = event_action_name(event) - if event.project diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 4e5d965ccbe..73cf6e87eb4 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -17,7 +17,7 @@ .pull-right .dropdown.inline - %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light - if @sort.present? = sort_options_hash[@sort] diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 5ea154c36b4..e3088848492 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -1,6 +1,6 @@ - if current_user .dropdown - %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown" } = icon('globe') %span.light Visibility: - if params[:visibility_level].present? @@ -20,7 +20,7 @@ - if @tags.present? .dropdown - %button.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle{ href: '#', "data-toggle" => "dropdown" } = icon('tags') %span.light Tags: - if params[:tag].present? diff --git a/app/views/groups/_group_lfs_settings.html.haml b/app/views/groups/_group_lfs_settings.html.haml index af57065f0fc..3c622ca5c3c 100644 --- a/app/views/groups/_group_lfs_settings.html.haml +++ b/app/views/groups/_group_lfs_settings.html.haml @@ -8,4 +8,4 @@ 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 + %span.descr This setting can be overridden in each project. diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 33fee334d93..2e7e5e5c309 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -18,8 +18,8 @@ .pull-right - if project.archived %span.label.label-warning archived - %span.label.label-gray - = repository_size(project) + %span.badge + = storage_counter(project.statistics.storage_size) = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 52ce26a20b1..d256d14609e 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -22,7 +22,7 @@ = render 'shared/members/access_request_buttons', source: @group = render 'shared/notifications/button', notification_setting: @notification_setting -%div.groups-header{ class: container_class } +.groups-header{ class: container_class } .top-area %ul.nav-links %li.active @@ -32,6 +32,10 @@ %li = link_to "#shared", 'data-toggle' => 'tab' do Shared Projects + - if @nested_groups.present? + %li + = link_to "#groups", 'data-toggle' => 'tab' do + Subgroups .nav-controls = form_tag request.path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false @@ -47,3 +51,8 @@ - if @shared_projects.present? .tab-pane#shared = render "shared_projects", projects: @shared_projects + + - if @nested_groups.present? + .tab-pane#groups + %ul.content-list + = render partial: 'shared/groups/group', collection: @nested_groups diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 65842a0479b..b74cc822295 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -1,8 +1,8 @@ -#modal-shortcuts.modal{tabindex: -1} +#modal-shortcuts.modal{ tabindex: -1 } .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h4 Keyboard Shortcuts %small @@ -82,7 +82,7 @@ .col-lg-4 %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut project', style: 'display:none' } + %tbody.hidden-shortcut.project{ style: 'display:none' } %tr %th %th Global Dashboard @@ -190,7 +190,7 @@ %td New issue .col-lg-4 %table.shortcut-mappings - %tbody{ class: 'hidden-shortcut network', style: 'display:none' } + %tbody.hidden-shortcut.network{ style: 'display:none' } %tr %th %th Network Graph @@ -240,7 +240,7 @@ .key shift j %td Scroll to bottom - %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } + %tbody.hidden-shortcut.issues{ style: 'display:none' } %tr %th %th Issues @@ -264,7 +264,7 @@ %td.shortcut .key l %td Change Label - %tbody{ class: 'hidden-shortcut merge_requests', style: 'display:none' } + %tbody.hidden-shortcut.merge_requests{ style: 'display:none' } %tr %th %th Merge Requests diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 070ed90da6d..dd1df46792b 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -182,7 +182,7 @@ .nav-controls = text_field_tag 'sample', nil, class: 'form-control' .dropdown - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span Sort by name = icon('chevron-down') %ul.dropdown-menu @@ -205,121 +205,121 @@ %h2#buttons Buttons .example - %button.btn.btn-default{:type => "button"} Default - %button.btn.btn-gray{:type => "button"} Gray - %button.btn.btn-primary{:type => "button"} Primary - %button.btn.btn-success{:type => "button"} Success - %button.btn.btn-info{:type => "button"} Info - %button.btn.btn-warning{:type => "button"} Warning - %button.btn.btn-danger{:type => "button"} Danger - %button.btn.btn-link{:type => "button"} Link + %button.btn.btn-default{ :type => "button" } Default + %button.btn.btn-gray{ :type => "button" } Gray + %button.btn.btn-primary{ :type => "button" } Primary + %button.btn.btn-success{ :type => "button" } Success + %button.btn.btn-info{ :type => "button" } Info + %button.btn.btn-warning{ :type => "button" } Warning + %button.btn.btn-danger{ :type => "button" } Danger + %button.btn.btn-link{ :type => "button" } Link %h2#dropdowns Dropdowns .example .clearfix .dropdown.inline.pull-left - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') %ul.dropdown-menu %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option .dropdown.inline.pull-right - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-align-right %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option .example %div .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') %ul.dropdown-menu.dropdown-menu-selectable %li - %a.is-active{href: "#"} + %a.is-active{ href: "#" } Dropdown Option .example %div .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Dropdown Title - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Filter results"} + %input.dropdown-input-field{ type: "search", placeholder: "Filter results" } = icon('search') .dropdown-content %ul %li - %a.is-active{href: "#"} + %a.is-active{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li.divider %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option .dropdown-footer %strong Tip: If an author is not a member of this project, you can still filter by his name while using the search field. .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown loading = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable.is-loading .dropdown-title %span Dropdown Title - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Filter results"} + %input.dropdown-input-field{ type: "search", placeholder: "Filter results" } = icon('search') .dropdown-content %ul %li - %a.is-active{href: "#"} + %a.is-active{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li.divider %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option %li - %a{href: "#"} + %a{ href: "#" } Dropdown Option .dropdown-footer %strong Tip: @@ -330,21 +330,21 @@ .example %div .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: {toggle: 'dropdown' } } Dropdown user = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-user .dropdown-title %span Dropdown Title - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Filter results"} + %input.dropdown-input-field{ type: "search", placeholder: "Filter results" } = icon('search') .dropdown-content %ul %li - %a.dropdown-menu-user-link.is-active{href: "#"} + %a.dropdown-menu-user-link.is-active{ href: "#" } = link_to_member_avatar(@user, size: 30) %strong.dropdown-menu-user-full-name = @user.name @@ -354,24 +354,24 @@ .example %div .dropdown.inline - %button.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Dropdown page 2 = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-user.dropdown-menu-paging.is-page-two .dropdown-page-one .dropdown-title - %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} + %button.dropdown-title-button.dropdown-menu-back{ aria: { label: "Go back" } } = icon('arrow-left') %span Dropdown Title - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Filter results"} + %input.dropdown-input-field{ type: "search", placeholder: "Filter results" } = icon('search') .dropdown-content %ul %li - %a.dropdown-menu-user-link.is-active{href: "#"} + %a.dropdown-menu-user-link.is-active{ href: "#" } = link_to_member_avatar(@user, size: 30) %strong.dropdown-menu-user-full-name = @user.name @@ -379,13 +379,13 @@ = @user.to_reference .dropdown-page-two .dropdown-title - %button.dropdown-title-button.dropdown-menu-back{aria: {label: "Go back"}} + %button.dropdown-title-button.dropdown-menu-back{ aria: { label: "Go back" } } = icon('arrow-left') %span Create label - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Name new label"} + %input.dropdown-input-field{ type: "search", placeholder: "Name new label" } .dropdown-content %button.btn.btn-primary Create @@ -393,16 +393,16 @@ .example %div .dropdown.inline - %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button#js-project-dropdown.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } Projects = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Go to project - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times') .dropdown-input - %input.dropdown-input-field{type: "search", placeholder: "Filter results"} + %input.dropdown-input-field{ type: "search", placeholder: "Filter results" } = icon('search') .dropdown-content .dropdown-loading @@ -486,22 +486,22 @@ .example %form.form-horizontal .form-group - %label.col-sm-2.control-label{:for => "inputEmail3"} Email + %label.col-sm-2.control-label{ :for => "inputEmail3" } Email .col-sm-10 - %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/ + %input#inputEmail3.form-control{ :placeholder => "Email", :type => "email" }/ .form-group - %label.col-sm-2.control-label{:for => "inputPassword3"} Password + %label.col-sm-2.control-label{ :for => "inputPassword3" } Password .col-sm-10 - %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/ + %input#inputPassword3.form-control{ :placeholder => "Password", :type => "password" }/ .form-group .col-sm-offset-2.col-sm-10 .checkbox %label - %input{:type => "checkbox"}/ + %input{ :type => "checkbox" }/ Remember me .form-group .col-sm-offset-2.col-sm-10 - %button.btn.btn-default{:type => "submit"} Sign in + %button.btn.btn-default{ :type => "submit" } Sign in .lead Form when label rendered above input @@ -510,16 +510,16 @@ .example %form .form-group - %label{:for => "exampleInputEmail1"} Email address - %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/ + %label{ :for => "exampleInputEmail1" } Email address + %input#exampleInputEmail1.form-control{ :placeholder => "Enter email", :type => "email" }/ .form-group - %label{:for => "exampleInputPassword1"} Password - %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/ + %label{ :for => "exampleInputPassword1" } Password + %input#exampleInputPassword1.form-control{ :placeholder => "Password", :type => "password" }/ .checkbox %label - %input{:type => "checkbox"}/ + %input{ :type => "checkbox" }/ Remember me - %button.btn.btn-default{:type => "submit"} Sign in + %button.btn.btn-default{ :type => "submit" } Sign in %h2#file File %h4 diff --git a/app/views/import/_githubish_status.html.haml b/app/views/import/_githubish_status.html.haml index f12f9482a51..864c5c0ff95 100644 --- a/app/views/import/_githubish_status.html.haml +++ b/app/views/import/_githubish_status.html.haml @@ -21,7 +21,7 @@ %th Status %tbody - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td = provider_project_link(provider, project.import_source) %td @@ -38,7 +38,7 @@ = project.human_import_status_name - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} + %tr{ id: "repo_#{repo.id}" } %td = provider_project_link(provider, repo.full_name) %td.import-target diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index ac09b71ae89..7f1b9ee7141 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -31,7 +31,7 @@ %th Status %tbody - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank' %td @@ -48,7 +48,7 @@ = project.human_import_status_name - @repos.each do |repo| - %tr{id: "repo_#{repo.owner}___#{repo.slug}"} + %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: "_blank" %td.import-target @@ -68,7 +68,7 @@ Import = icon('spinner spin', class: 'loading-icon') - @incompatible_repos.each do |repo| - %tr{id: "repo_#{repo.owner}___#{repo.slug}"} + %tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %td = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank' %td.import-target diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index c8a6fa1aa9e..97e5e51abe0 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -29,7 +29,7 @@ %th Status %tbody - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td = project.import_source %td @@ -46,7 +46,7 @@ = project.human_import_status_name - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} + %tr{ id: "repo_#{repo.id}" } %td = repo.name %td.import-target diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index d31fc2e6adb..d5b88709a34 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -24,7 +24,7 @@ %th Status %tbody - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" %td @@ -41,7 +41,7 @@ = project.human_import_status_name - @repos.each do |repo| - %tr{id: "repo_#{repo["id"]}"} + %tr{ id: "repo_#{repo["id"]}" } %td = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" %td.import-target diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 5d2f149cd5f..336becd229e 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -45,7 +45,7 @@ %p Upload <code>GoogleCodeProjectHosting.json</code> here: %p - %input{type: "file", name: "dump_file", id: "dump_file"} + %input{ type: "file", name: "dump_file", id: "dump_file" } %li %p Do you want to customize how Google Code email addresses and usernames are imported into GitLab? diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index e79f122940a..9f1507cade6 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -34,7 +34,7 @@ %th Status %tbody - @already_added_projects.each do |project| - %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %td = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" %td @@ -51,7 +51,7 @@ = project.human_import_status_name - @repos.each do |repo| - %tr{id: "repo_#{repo.id}"} + %tr{ id: "repo_#{repo.id}" } %td = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" %td.import-target @@ -61,7 +61,7 @@ Import = icon("spinner spin", class: "loading-icon") - @incompatible_repos.each do |repo| - %tr{id: "repo_#{repo.id}"} + %tr{ id: "repo_#{repo.id}" } %td = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" %td.import-target diff --git a/app/views/kaminari/gitlab/_next_page.html.haml b/app/views/kaminari/gitlab/_next_page.html.haml index 125f09777ba..c93dc7a50e8 100644 --- a/app/views/kaminari/gitlab/_next_page.html.haml +++ b/app/views/kaminari/gitlab/_next_page.html.haml @@ -6,8 +6,8 @@ -# per_page: number of items to fetch per page -# remote: data-remote - if current_page.last? - %li{ class: "next disabled" } + %li.next.disabled %span= raw(t 'views.pagination.next') - else - %li{ class: "next" } + %li.next = link_to raw(t 'views.pagination.next'), url, rel: 'next', remote: remote diff --git a/app/views/kaminari/gitlab/_page.html.haml b/app/views/kaminari/gitlab/_page.html.haml index 750aed8f329..cefe0066a8f 100644 --- a/app/views/kaminari/gitlab/_page.html.haml +++ b/app/views/kaminari/gitlab/_page.html.haml @@ -6,5 +6,5 @@ -# total_pages: total number of pages -# per_page: number of items to fetch per page -# remote: data-remote -%li{class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}"} - = link_to page, url, {remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil} +%li{ class: "page#{' active' if page.current?}#{' sibling' if page.next? || page.prev?}" } + = link_to page, url, { remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil } diff --git a/app/views/kaminari/gitlab/_paginator.html.haml b/app/views/kaminari/gitlab/_paginator.html.haml index f5e0d2ed3f3..8fe6bd653ae 100644 --- a/app/views/kaminari/gitlab/_paginator.html.haml +++ b/app/views/kaminari/gitlab/_paginator.html.haml @@ -6,7 +6,7 @@ -# remote: data-remote -# paginator: the paginator that renders the pagination tags inside = paginator.render do - %div.gl-pagination + .gl-pagination %ul.pagination.clearfix - unless current_page.first? = first_page_tag unless total_pages < 5 # As kaminari will always show the first 5 pages @@ -19,4 +19,3 @@ = next_page_tag - unless current_page.last? = last_page_tag unless total_pages < 5 - diff --git a/app/views/kaminari/gitlab/_prev_page.html.haml b/app/views/kaminari/gitlab/_prev_page.html.haml index 7edf10498a8..b7c6caf7ff4 100644 --- a/app/views/kaminari/gitlab/_prev_page.html.haml +++ b/app/views/kaminari/gitlab/_prev_page.html.haml @@ -6,8 +6,8 @@ -# per_page: number of items to fetch per page -# remote: data-remote - if current_page.first? - %li{ class: "prev disabled" } + %li.prev.disabled %span= raw(t 'views.pagination.previous') - else - %li{ class: "prev" } + %li.prev = link_to raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 3e488cf73b9..3096f0ee19e 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,27 +1,27 @@ - page_description brand_title unless page_description - site_name = "GitLab" -%head{prefix: "og: http://ogp.me/ns#"} - %meta{charset: "utf-8"} - %meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'} +%head{ prefix: "og: http://ogp.me/ns#" } + %meta{ charset: "utf-8" } + %meta{ 'http-equiv' => 'X-UA-Compatible', content: 'IE=edge' } -# Open Graph - http://ogp.me/ - %meta{property: 'og:type', content: "object"} - %meta{property: 'og:site_name', content: site_name} - %meta{property: 'og:title', content: page_title} - %meta{property: 'og:description', content: page_description} - %meta{property: 'og:image', content: page_image} - %meta{property: 'og:url', content: request.base_url + request.fullpath} + %meta{ property: 'og:type', content: "object" } + %meta{ property: 'og:site_name', content: site_name } + %meta{ property: 'og:title', content: page_title } + %meta{ property: 'og:description', content: page_description } + %meta{ property: 'og:image', content: page_image } + %meta{ property: 'og:url', content: request.base_url + request.fullpath } -# Twitter Card - https://dev.twitter.com/cards/types/summary - %meta{property: 'twitter:card', content: "summary"} - %meta{property: 'twitter:title', content: page_title} - %meta{property: 'twitter:description', content: page_description} - %meta{property: 'twitter:image', content: page_image} + %meta{ property: 'twitter:card', content: "summary" } + %meta{ property: 'twitter:title', content: page_title } + %meta{ property: 'twitter:description', content: page_description } + %meta{ property: 'twitter:image', content: page_image } = page_card_meta_tags %title= page_title(site_name) - %meta{name: "description", content: page_description} + %meta{ name: "description", content: page_description } = favicon_link_tag 'favicon.ico' @@ -36,20 +36,20 @@ = csrf_meta_tags - unless browser.safari? - %meta{name: 'referrer', content: 'origin-when-cross-origin'} - %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} - %meta{name: 'theme-color', content: '#474D57'} + %meta{ name: 'referrer', content: 'origin-when-cross-origin' } + %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1' } + %meta{ name: 'theme-color', content: '#474D57' } -# Apple Safari/iOS home screen icons = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon' = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76' = favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120' = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152' - %link{rel: 'mask-icon', href: image_path('logo.svg'), color: 'rgb(226, 67, 41)'} + %link{ rel: 'mask-icon', href: image_path('logo.svg'), color: 'rgb(226, 67, 41)' } -# Windows 8 pinned site tile - %meta{name: 'msapplication-TileImage', content: image_path('msapplication-tile.png')} - %meta{name: 'msapplication-TileColor', content: '#30353E'} + %meta{ name: 'msapplication-TileImage', content: image_path('msapplication-tile.png') } + %meta{ name: 'msapplication-TileColor', content: '#30353E' } = yield :meta_tags diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 8e65bd12c56..0e64ebd71b8 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -6,7 +6,7 @@ - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) } - if @project && @project.persisted? - project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: namespace_project_issues_path(@project.namespace, @project), mr_path: namespace_project_merge_requests_path(@project.namespace, @project) } -.search.search-form{class: "#{'has-location-badge' if label.present?}"} +.search.search-form{ class: "#{'has-location-badge' if label.present?}" } = form_tag search_path, method: :get, class: 'navbar-form' do |f| .search-input-container - if label.present? @@ -44,4 +44,4 @@ = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref = button_tag 'Go' if ENV['RAILS_ENV'] == 'test' - .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } + .search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 6c2285fa2b6..935517d4913 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,7 +1,7 @@ !!! 5 %html{ lang: "en", class: "#{page_class}" } = render "layouts/head" - %body{class: "#{user_application_theme}", data: {page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}"}} + %body{ class: "#{user_application_theme}", data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } } = Gon::Base.render_data -# Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body. diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index afd9958f073..3368a9beb29 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,7 +1,7 @@ !!! 5 -%html{ lang: "en", class: "devise-layout-html"} +%html.devise-layout-html = render "layouts/head" - %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page }} + %body.ui_charcoal.login-page.application.navless{ data: { page: body_data_page } } .page-wrap = Gon::Base.render_data = render "layouts/header/empty" diff --git a/app/views/layouts/devise_empty.html.haml b/app/views/layouts/devise_empty.html.haml index 6bd427b02ac..7466423a934 100644 --- a/app/views/layouts/devise_empty.html.haml +++ b/app/views/layouts/devise_empty.html.haml @@ -1,5 +1,5 @@ !!! 5 -%html{ lang: "en"} +%html{ lang: "en" } = render "layouts/head" %body.ui_charcoal.login-page.application.navless = Gon::Base.render_data diff --git a/app/views/layouts/devise_mailer.html.haml b/app/views/layouts/devise_mailer.html.haml index c258eafdd51..e1e1f9ae516 100644 --- a/app/views/layouts/devise_mailer.html.haml +++ b/app/views/layouts/devise_mailer.html.haml @@ -1,7 +1,7 @@ !!! 5 %html %head - %meta(content='text/html; charset=UTF-8' http-equiv='Content-Type') + %meta{ content: 'text/html; charset=UTF-8', 'http-equiv'=> 'Content-Type' } = stylesheet_link_tag 'mailers/devise' %body @@ -9,7 +9,7 @@ %tr %td %table#header - %td{valign: "top"} + %td{ valign: "top" } = image_tag('mailers/gitlab_header_logo.png', id: 'logo', alt: 'GitLab Wordmark') %table#body diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index a3b925f6afd..6d9ec043590 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,7 +1,7 @@ !!! 5 -%html{ lang: "en"} +%html{ lang: "en" } %head - %meta{:content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport"} + %meta{ :content => "width=device-width, initial-scale=1, maximum-scale=1", :name => "viewport" } %title= yield(:title) :css body { diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 5456be77aab..f4e0244596c 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,11 +1,11 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - %a{ href: "#content-body", tabindex: "1", class: "sr-only gl-accessibility" } Skip to content - %div{ class: "container-fluid" } + %a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content + .container-fluid .header-content %button.side-nav-toggle{ type: 'button', "aria-label" => "Toggle global navigation" } %span.sr-only Toggle navigation = icon('bars') - %button.navbar-toggle{type: 'button'} + %button.navbar-toggle{ type: 'button' } %span.sr-only Toggle navigation = icon('ellipsis-v') diff --git a/app/views/layouts/nav/_admin_settings.html.haml b/app/views/layouts/nav/_admin_settings.html.haml index 38e9b80d129..9de0e12a826 100644 --- a/app/views/layouts/nav/_admin_settings.html.haml +++ b/app/views/layouts/nav/_admin_settings.html.haml @@ -1,6 +1,6 @@ .controls .dropdown.admin-settings-dropdown - %a.dropdown-new.btn.btn-default{href: '#', 'data-toggle' => 'dropdown'} + %a.dropdown-new.btn.btn-default{ href: '#', 'data-toggle' => 'dropdown' } = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index 817e4bebb05..205d23178d2 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -35,3 +35,7 @@ = link_to dashboard_snippets_path, title: 'Snippets' do %span Snippets + + = link_to help_path, title: 'About GitLab CE', class: 'about-gitlab' do + %span + About GitLab CE diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index 1579d8f1662..30feb6813b4 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -5,7 +5,7 @@ - if can_admin_group || can_edit .controls .dropdown.group-settings-dropdown - %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} + %a.dropdown-new.btn.btn-default#group-settings-button{ href: '#', 'data-toggle' => 'dropdown' } = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index cc1571cbb4f..3c8c7b8f25e 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,7 +1,7 @@ - if current_user .controls .dropdown.project-settings-dropdown - %a.dropdown-new.btn.btn-default#project-settings-button{href: '#', 'data-toggle' => 'dropdown'} + %a.dropdown-new.btn.btn-default#project-settings-button{ href: '#', 'data-toggle' => 'dropdown' } = icon('cog') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 1ec4c3f0c67..76268c1b705 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -1,14 +1,14 @@ -%html{lang: "en"} +%html{ lang: "en" } %head - %meta{content: "text/html; charset=utf-8", "http-equiv" => "Content-Type"} + %meta{ content: "text/html; charset=utf-8", "http-equiv" => "Content-Type" } %title GitLab = stylesheet_link_tag 'notify' = yield :head %body - %div.content + .content = yield - %div.footer{style: "margin-top: 10px;"} + .footer{ style: "margin-top: 10px;" } %p — %br diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml index 4bf7c1f4d64..a744c4be9d6 100644 --- a/app/views/notify/build_fail_email.html.haml +++ b/app/views/notify/build_fail_email.html.haml @@ -1,5 +1,5 @@ - content_for :header do - %h1{style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} + %h1{ style: "background: #c40834; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" } GitLab (build failed) %h3 diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml index 252a5b7152c..8c2e6db1426 100644 --- a/app/views/notify/build_success_email.html.haml +++ b/app/views/notify/build_success_email.html.haml @@ -1,5 +1,5 @@ - content_for :header do - %h1{style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;"} + %h1{ style: "background: #38CF5B; color: #FFF; font: normal 20px Helvetica, Arial, sans-serif; margin: 0; padding: 5px 10px; line-height: 32px; font-size: 16px;" } GitLab (build successful) %h3 diff --git a/app/views/notify/links/ci/builds/_build.html.haml b/app/views/notify/links/ci/builds/_build.html.haml index 38cd4e5e145..d35b3839171 100644 --- a/app/views/notify/links/ci/builds/_build.html.haml +++ b/app/views/notify/links/ci/builds/_build.html.haml @@ -1,2 +1,2 @@ -%a{href: pipeline_build_url(pipeline, build), style: "color:#3777b0;text-decoration:none;"} +%a{ href: pipeline_build_url(pipeline, build), style: "color:#3777b0;text-decoration:none;" } = build.name diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index f42b150c0d6..d1855568215 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -1,7 +1,7 @@ - if current_application_settings.email_author_in_body %div #{link_to @issue.author_name, user_url(@issue.author)} wrote: --if @issue.description +- if @issue.description = markdown(@issue.description, pipeline: :email, author: @issue.author) - if @issue.assignee_id.present? diff --git a/app/views/notify/new_mention_in_issue_email.html.haml b/app/views/notify/new_mention_in_issue_email.html.haml index 4f3d36bd9ca..02f21baa368 100644 --- a/app/views/notify/new_mention_in_issue_email.html.haml +++ b/app/views/notify/new_mention_in_issue_email.html.haml @@ -4,7 +4,7 @@ - if current_application_settings.email_author_in_body %div #{link_to @issue.author_name, user_url(@issue.author)} wrote: --if @issue.description +- if @issue.description = markdown(@issue.description, pipeline: :email, author: @issue.author) - if @issue.assignee_id.present? diff --git a/app/views/notify/new_mention_in_merge_request_email.html.haml b/app/views/notify/new_mention_in_merge_request_email.html.haml index 32aedb9e6b9..cbd434be02a 100644 --- a/app/views/notify/new_mention_in_merge_request_email.html.haml +++ b/app/views/notify/new_mention_in_merge_request_email.html.haml @@ -11,5 +11,5 @@ %p Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} --if @merge_request.description +- if @merge_request.description = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author) diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 158404de396..8890b300f7d 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -8,5 +8,5 @@ %p Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} --if @merge_request.description +- if @merge_request.description = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author) diff --git a/app/views/notify/pipeline_failed_email.html.haml b/app/views/notify/pipeline_failed_email.html.haml index 001d9c48555..82c7fe229b8 100644 --- a/app/views/notify/pipeline_failed_email.html.haml +++ b/app/views/notify/pipeline_failed_email.html.haml @@ -1,9 +1,9 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -%html{lang: "en"} +%html{ lang: "en" } %head - %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ - %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ + %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ + %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ @@ -41,139 +41,139 @@ padding-right: 10px !important; } } - %body{style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table#body{border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } %tbody %tr.line - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}  + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  %tr.header - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55"}/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/ %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table.wrapper{border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.content{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } %tbody %tr.alert - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;background-color:#d22f57;color:#ffffff;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} - %img{alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } + %img{ alt: "x", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } Your pipeline has failed. %tr.spacer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } %tr.section - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.info{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) - %a.muted{href: namespace_url, style: "color:#333333;text-decoration:none;"} + %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } = namespace_name \/ - %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} + %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } = @project.name %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } = @pipeline.ref %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = @pipeline.short_sha - if @merge_request in - %a{href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;"} + %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } = @merge_request.to_reference - .commit{style: "color:#5c5c5c;font-weight:300;"} + .commit{ style: "color:#5c5c5c;font-weight:300;" } = @pipeline.git_commit_message.truncate(50) %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - commit = @pipeline.commit - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author - %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} + %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } = commit.author.name - else %span = commit.author_name %tr.spacer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } - failed = @pipeline.statuses.latest.failed %tr.pre-section - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 0;" } Pipeline - %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = "\##{@pipeline.id}" had = failed.size failed = "#{'build'.pluralize(failed.size)}." %tr.warning - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;border:1px solid #ededed;border-bottom:0;border-radius:3px 3px 0 0;overflow:hidden;background-color:#fdf4f6;color:#d22852;font-size:14px;line-height:1.4;text-align:center;padding:8px 15px;" } Logs may contain sensitive data. Please consider before forwarding this email. %tr.section - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;"} - %table.builds{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;border-top:0;border-radius:0 0 3px 3px;" } + %table.builds{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:collapse;" } %tbody - failed.each do |build| %tr.build-state - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;"} - %img{alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;padding-right:5px;" } + %img{ alt: "x", height: "10", src: image_url('mailers/ci_pipeline_notif_v1/icon-x-red.gif'), style: "display:block;", width: "10" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#8c8c8c;font-weight:500;font-size:15px;vertical-align:middle;" } = build.stage - %td{align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;"} + %td{ align: "right", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:20px 0;color:#8c8c8c;font-weight:500;font-size:15px;" } = render "notify/links/#{build.to_partial_path}", pipeline: @pipeline, build: build %tr.build-log - if build.has_trace? - %td{colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;"} - %pre{style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;"} + %td{ colspan: "2", style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 0 15px;" } + %pre{ style: "font-family:Monaco,'Lucida Console','Courier New',Courier,monospace;background-color:#fafafa;border-radius:3px;overflow:hidden;white-space:pre-wrap;word-break:break-all;font-size:13px;line-height:1.4;padding:12px;color:#333333;margin:0;" } = build.trace_html(last_lines: 10).html_safe - else - %td{colspan: "2"} + %td{ colspan: "2" } %tr.footer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ %div - %a{href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;"} Manage all notifications + %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications · - %a{href: help_url, style: "color:#3777b0;text-decoration:none;"} Help + %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help %div You're receiving this email because of your account on = succeed "." do - %a{href: root_url, style: "color:#3777b0;text-decoration:none;"}= Gitlab.config.gitlab.host + %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host diff --git a/app/views/notify/pipeline_success_email.html.haml b/app/views/notify/pipeline_success_email.html.haml index 56c1949ab2b..6dddb3b6373 100644 --- a/app/views/notify/pipeline_success_email.html.haml +++ b/app/views/notify/pipeline_success_email.html.haml @@ -1,9 +1,9 @@ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -%html{lang: "en"} +%html{ lang: "en" } %head - %meta{content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/ - %meta{content: "width=device-width, initial-scale=1", name: "viewport"}/ - %meta{content: "IE=edge", "http-equiv" => "X-UA-Compatible"}/ + %meta{ content: "text/html; charset=UTF-8", "http-equiv" => "Content-Type" }/ + %meta{ content: "width=device-width, initial-scale=1", name: "viewport" }/ + %meta{ content: "IE=edge", "http-equiv" => "X-UA-Compatible" }/ %title= message.subject :css /* CLIENT-SPECIFIC STYLES */ @@ -41,114 +41,114 @@ padding-right: 10px !important; } } - %body{style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table#body{border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;"} + %body{ style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;height:100%;font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table#body{ border: "0", cellpadding: "0", cellspacing: "0", style: "background-color:#fafafa;margin:0;padding:0;text-align:center;min-width:640px;width:100%;" } %tbody %tr.line - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;"}  + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#6b4fbb;height:4px;font-size:4px;line-height:4px;" }  %tr.header - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55"}/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "50", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo.gif'), width: "55" }/ %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;"} - %table.wrapper{border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;" } + %table.wrapper{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:640px;margin:0 auto;border-collapse:separate;border-spacing:0;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.content{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;background-color:#ffffff;text-align:left;padding:18px 25px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.content{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;border-collapse:separate;border-spacing:0;" } %tbody %tr.success - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:10px;border-radius:3px;font-size:14px;line-height:1.3;text-align:center;overflow:hidden;color:#ffffff;background-color:#31af64;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;margin:0 auto;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;"} - %img{alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;padding-right:5px;" } + %img{ alt: "✓", height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-check-green-inverted.gif'), style: "display:block;", width: "13" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;vertical-align:middle;color:#ffffff;text-align:center;" } Your pipeline has passed. %tr.spacer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } %tr.section - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;"} - %table.info{border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:0 15px;border:1px solid #ededed;border-radius:3px;overflow:hidden;" } + %table.info{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;"} Project - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;" } Project + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;" } - namespace_name = @project.group ? @project.group.name : @project.namespace.owner.name - namespace_url = @project.group ? group_url(@project.group) : user_url(@project.namespace.owner) - %a.muted{href: namespace_url, style: "color:#333333;text-decoration:none;"} + %a.muted{ href: namespace_url, style: "color:#333333;text-decoration:none;" } = namespace_name \/ - %a.muted{href: project_url(@project), style: "color:#333333;text-decoration:none;"} + %a.muted{ href: project_url(@project), style: "color:#333333;text-decoration:none;" } = @project.name %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Branch - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Branch + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a.muted{href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-branch-gray.gif'), style: "display:block;", width: "13", alt: "Branch icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a.muted{ href: commits_url(@pipeline), style: "color:#333333;text-decoration:none;" } = @pipeline.ref %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Commit - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Commit + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img{height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} - %a{href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img{ height: "13", src: image_url('mailers/ci_pipeline_notif_v1/icon-commit-gray.gif'), style: "display:block;", width: "13", alt: "Commit icon" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } + %a{ href: commit_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = @pipeline.short_sha - if @merge_request in - %a{href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;"} + %a{ href: merge_request_url(@merge_request), style: "color:#3777b0;text-decoration:none;" } = @merge_request.to_reference - .commit{style: "color:#5c5c5c;font-weight:300;"} + .commit{ style: "color:#5c5c5c;font-weight:300;" } = @pipeline.git_commit_message.truncate(50) %tr - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;"} Author - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;"} - %table.img{border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;border-top:1px solid #ededed;" } Author + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;color:#8c8c8c;font-weight:300;padding:14px 0;margin:0;color:#333333;font-weight:400;width:75%;padding-left:5px;border-top:1px solid #ededed;" } + %table.img{ border: "0", cellpadding: "0", cellspacing: "0", style: "border-collapse:collapse;" } %tbody %tr - commit = @pipeline.commit - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;"} - %img.avatar{height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24"}/ - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;padding-right:5px;" } + %img.avatar{ height: "24", src: avatar_icon(commit.author || commit.author_email, 24), style: "display:block;border-radius:12px;margin:-2px 0;", width: "24", alt: "Avatar" }/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;font-size:15px;line-height:1.4;vertical-align:middle;" } - if commit.author - %a.muted{href: user_url(commit.author), style: "color:#333333;text-decoration:none;"} + %a.muted{ href: user_url(commit.author), style: "color:#333333;text-decoration:none;" } = commit.author.name - else %span = commit.author_name %tr.spacer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;height:18px;font-size:18px;line-height:18px;" } %tr.success-message - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;"} + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333333;font-size:15px;font-weight:400;line-height:1.4;padding:15px 5px;text-align:center;" } - build_count = @pipeline.statuses.latest.size - stage_count = @pipeline.stages_count Pipeline - %a{href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;"} + %a{ href: pipeline_url(@pipeline), style: "color:#3777b0;text-decoration:none;" } = "\##{@pipeline.id}" successfully completed = "#{build_count} #{'build'.pluralize(build_count)}" in = "#{stage_count} #{'stage'.pluralize(stage_count)}." %tr.footer - %td{style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;"} - %img{alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90"}/ + %td{ style: "font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;padding:25px 0;font-size:13px;line-height:1.6;color:#5c5c5c;" } + %img{ alt: "GitLab", height: "33", src: image_url('mailers/ci_pipeline_notif_v1/gitlab-logo-full-horizontal.gif'), style: "display:block;margin:0 auto 1em;", width: "90" }/ %div - %a{href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;"} Manage all notifications + %a{ href: profile_notifications_url, style: "color:#3777b0;text-decoration:none;" } Manage all notifications · - %a{href: help_url, style: "color:#3777b0;text-decoration:none;"} Help + %a{ href: help_url, style: "color:#3777b0;text-decoration:none;" } Help %div You're receiving this email because of your account on = succeed "." do - %a{href: root_url, style: "color:#3777b0;text-decoration:none;"}= Gitlab.config.gitlab.host + %a{ href: root_url, style: "color:#3777b0;text-decoration:none;" }= Gitlab.config.gitlab.host diff --git a/app/views/notify/project_was_not_exported_email.text.haml b/app/views/notify/project_was_not_exported_email.text.haml index b27cb620b9e..27785165c2d 100644 --- a/app/views/notify/project_was_not_exported_email.text.haml +++ b/app/views/notify/project_was_not_exported_email.text.haml @@ -3,4 +3,4 @@ = "The errors we encountered were:" - @errors.each do |error| - #{error}
\ No newline at end of file + #{error} diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 25883de257c..36858fa6f34 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -29,7 +29,7 @@ %ul - @message.diffs.each do |diff| %li.file-stats - %a{href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff.file_path)}" } + %a{ href: "#{@message.target_url if @message.disable_diffs?}##{hexdigest(diff.file_path)}" } - if diff.deleted_file %span.deleted-file − @@ -54,8 +54,8 @@ %h4 Changes: - diff_files.each do |diff_file| - file_hash = hexdigest(diff_file.file_path) - %li{id: file_hash} - %a{href: @message.target_url + "##{file_hash}"}< + %li{ id: file_hash } + %a{ href: @message.target_url + "##{file_hash}" }< - if diff_file.deleted_file %strong< = diff_file.old_path diff --git a/app/views/profiles/chat_names/new.html.haml b/app/views/profiles/chat_names/new.html.haml index f635acf96e2..5bf22aa94f1 100644 --- a/app/views/profiles/chat_names/new.html.haml +++ b/app/views/profiles/chat_names/new.html.haml @@ -1,5 +1,5 @@ %h3.page-title Authorization required -%main{:role => "main"} +%main{ :role => "main" } %p.h4 Authorize %strong.text-info= @chat_name_params[:chat_name] diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 93187873501..71b224a413b 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -17,5 +17,5 @@ %hr %h5 Your SSH keys (#{@keys.count}) - %div.append-bottom-default + .append-bottom-default = render 'key_table' diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index d79a1a9f368..5c5e5940365 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -3,7 +3,7 @@ %div - if @user.errors.any? - %div.alert.alert-danger + .alert.alert-danger %ul - @user.errors.full_messages.each do |msg| %li= msg diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 2afa026847a..feadd863b00 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -1,7 +1,7 @@ - page_title 'Preferences' = render 'profiles/head' -= form_for @user, url: profile_preferences_path, remote: true, method: :put, html: {class: 'row prepend-top-default js-preferences-form'} do |f| += form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 Application theme @@ -10,7 +10,7 @@ .col-lg-9.application-theme - Gitlab::Themes.each do |theme| = label_tag do - .preview{class: theme.css_class} + .preview{ class: theme.css_class } = f.radio_button :theme_id, theme.id = theme.name .col-sm-12 diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 578af9fe98d..2385a90401e 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -30,7 +30,7 @@ The maximum file size allowed is 200KB. - if @user.avatar? %hr - = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-gray" + = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?" }, method: :delete, class: "btn btn-gray" %hr .row .col-lg-3.profile-settings-sidebar @@ -69,7 +69,7 @@ %span.help-block We also use email for avatar detection if no avatar is uploaded. .form-group = f.label :public_email, class: "label-light" - = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show on profile'}, class: "select2" + = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), { include_blank: 'Do not show on profile' }, class: "select2" %span.help-block This email will be displayed on your public profile. .form-group = f.label :skype, class: "label-light" @@ -101,14 +101,14 @@ .modal-dialog .modal-content .modal-header - %button.close{:type => "button", :'data-dismiss' => "modal"} + %button.close{ :type => "button", :'data-dismiss' => "modal" } %span × %h4.modal-title Position and size your new avatar .modal-body .profile-crop-image-container - %img.modal-profile-crop-image + %img.modal-profile-crop-image{ alt: "Avatar cropper" } .crop-controls .btn-group %button.btn.btn-primary{ data: { method: "zoom", option: "0.1" } } @@ -116,5 +116,5 @@ %button.btn.btn-primary{ data: { method: "zoom", option: "-0.1" } } %span.fa.fa-search-minus .modal-footer - %button.btn.btn-primary.js-upload-user-avatar{:type => "button"} + %button.btn.btn-primary.js-upload-user-avatar{ :type => "button" } Set new profile picture diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 4f15f2997fb..0ea733cb978 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -9,7 +9,7 @@ = render 'shared/event_filter' - .content_list.project-activity{:"data-href" => activity_project_path(@project)} + .content_list.project-activity{ :"data-href" => activity_project_path(@project) } = spinner :javascript diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml index e74fd5b93ea..c24a496486c 100644 --- a/app/views/projects/_bitbucket_import_modal.html.haml +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -1,8 +1,8 @@ -%div#bitbucket_import_modal.modal +#bitbucket_import_modal.modal .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3 Import projects from Bitbucket .modal-body To enable importing projects from Bitbucket, @@ -10,4 +10,4 @@ as administrator you need to configure - else ask your GitLab administrator to configure - == #{link_to 'OAuth integration', help_page_path("integration/bitbucket")}. + = link_to 'OAuth integration', help_page_path("integration/bitbucket") diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml index d2c1e943db1..e2b73cee5a9 100644 --- a/app/views/projects/_customize_workflow.html.haml +++ b/app/views/projects/_customize_workflow.html.haml @@ -1,5 +1,5 @@ .row-content-block.project-home-empty - %div.text-center{ class: container_class } + .text-center{ class: container_class } %h4 Customize your workflow! %p diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml index 08e2fc48be7..dbb33090670 100644 --- a/app/views/projects/_find_file_link.html.haml +++ b/app/views/projects/_find_file_link.html.haml @@ -1,3 +1,3 @@ -= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
- = icon('search')
- %span Find File
+= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do + = icon('search') + %span Find File diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml index e9f39b16aa7..00aef66e1f8 100644 --- a/app/views/projects/_gitlab_import_modal.html.haml +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -1,8 +1,8 @@ -%div#gitlab_import_modal.modal +#gitlab_import_modal.modal .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3 Import projects from GitLab.com .modal-body To enable importing projects from GitLab.com, @@ -10,4 +10,4 @@ as administrator you need to configure - else ask your GitLab administrator to configure - == #{link_to 'OAuth integration', help_page_path("integration/gitlab")}. + = link_to 'OAuth integration', help_page_path("integration/gitlab") diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 0d1f2b70018..1b9d87e9969 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -5,7 +5,7 @@ = project_icon(@project, alt: @project.name, class: 'avatar s70 avatar-tile') %h1.project-title = @project.name - %span.visibility-icon.has-tooltip{data: { container: 'body' }, title: visibility_icon_description(@project)} + %span.visibility-icon.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) } = visibility_level_icon(@project.visibility_level, fw: false) .project-home-desc diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 58d961d93ca..085f79de785 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -15,23 +15,23 @@ %li.pull-right .toolbar-group - = markdown_toolbar_button({icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) - = markdown_toolbar_button({icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) - = markdown_toolbar_button({icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) - = markdown_toolbar_button({icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" }) - = markdown_toolbar_button({icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) - = markdown_toolbar_button({icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) - = markdown_toolbar_button({icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) + = markdown_toolbar_button({ icon: "bold fw", data: { "md-tag" => "**" }, title: "Add bold text" }) + = markdown_toolbar_button({ icon: "italic fw", data: { "md-tag" => "*" }, title: "Add italic text" }) + = markdown_toolbar_button({ icon: "quote-right fw", data: { "md-tag" => "> ", "md-prepend" => true }, title: "Insert a quote" }) + = markdown_toolbar_button({ icon: "code fw", data: { "md-tag" => "`", "md-block" => "```" }, title: "Insert code" }) + = markdown_toolbar_button({ icon: "list-ul fw", data: { "md-tag" => "* ", "md-prepend" => true }, title: "Add a bullet list" }) + = markdown_toolbar_button({ icon: "list-ol fw", data: { "md-tag" => "1. ", "md-prepend" => true }, title: "Add a numbered list" }) + = markdown_toolbar_button({ icon: "check-square-o fw", data: { "md-tag" => "* [ ] ", "md-prepend" => true }, title: "Add a task list" }) .toolbar-group %button.toolbar-btn.js-zen-enter.has-tooltip.hidden-xs{ type: "button", tabindex: -1, aria: { label: "Go full screen" }, title: "Go full screen", data: { container: "body" } } - =icon("arrows-alt fw") + = icon("arrows-alt fw") .md-write-holder = yield - .md.md-preview-holder.js-md-preview.hide{class: (preview_class if defined?(preview_class))} + .md.md-preview-holder.js-md-preview.hide{ class: (preview_class if defined?(preview_class)) } - if defined?(referenced_users) && referenced_users - %div.referenced-users.hide + .referenced-users.hide %span = icon("exclamation-triangle") You are about to add diff --git a/app/views/projects/_wiki.html.haml b/app/views/projects/_wiki.html.haml index f00422dd7c0..41d42740f61 100644 --- a/app/views/projects/_wiki.html.haml +++ b/app/views/projects/_wiki.html.haml @@ -7,7 +7,7 @@ - else - can_create_wiki = can?(current_user, :create_wiki, @project) .project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] } - %div.text-center{ class: container_class } + .text-center{ class: container_class } %h4 This project does not have a wiki homepage yet - if can_create_wiki diff --git a/app/views/projects/artifacts/_tree_directory.html.haml b/app/views/projects/artifacts/_tree_directory.html.haml index def493c56f5..9e49c93388a 100644 --- a/app/views/projects/artifacts/_tree_directory.html.haml +++ b/app/views/projects/artifacts/_tree_directory.html.haml @@ -1,6 +1,6 @@ - path_to_directory = browse_namespace_project_build_artifacts_path(@project.namespace, @project, @build, path: directory.path) -%tr.tree-item{ 'data-link' => path_to_directory} +%tr.tree-item{ 'data-link' => path_to_directory } %td.tree-item-file-name = tree_icon('folder', '755', directory.name) %span.str-truncated diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index ede01dcc1aa..d0ff14e45e6 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -8,7 +8,7 @@ Download artifacts archive .tree-holder - %div.tree-content-holder + .tree-content-holder %table.table.tree-table %thead %tr diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index f63802ac88b..23f54553014 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -36,7 +36,7 @@ %td.line-numbers - line_count = blame_group[:lines].count - (current_line...(current_line + line_count)).each do |i| - %a.diff-line-num{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i} + %a.diff-line-num{ href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } = icon("link") = i \ diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index 149ee7c59d6..350bdf5f836 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -22,7 +22,7 @@ - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) = render blob_commit, project: @project, ref: @ref -%div#blob-content-holder.blob-content-holder +#blob-content-holder.blob-content-holder %article.file-holder .file-title = blob_icon blob.mode, blob.name diff --git a/app/views/projects/blob/_image.html.haml b/app/views/projects/blob/_image.html.haml index 4c356d1f07f..a486b2fe491 100644 --- a/app/views/projects/blob/_image.html.haml +++ b/app/views/projects/blob/_image.html.haml @@ -5,11 +5,11 @@ - # be wrong/strange if RawController modified the data. - blob.load_all_data!(@repository) - blob = sanitize_svg(blob) - %img{src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} + %img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}", alt: "#{blob.name}" } - else .nothing-here-block The SVG could not be displayed as it is too large, you can #{link_to('view the raw file', namespace_project_raw_path(@project.namespace, @project, @id), target: '_blank')} instead. - else - %img{src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path))} + %img{ src: namespace_project_raw_path(@project.namespace, @project, tree_join(@commit.id, blob.path)), alt: "#{blob.name}" } diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index 84694203d7d..7f470b890ba 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -2,7 +2,7 @@ .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title Create New Directory .modal-body = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form js-quick-submit js-requires-input' do diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 2e1f32fd15e..db6662a95ac 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -2,7 +2,7 @@ .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title Delete #{@blob.name} .modal-body diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index 57a27ec904e..61a7ffdd0ab 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -2,7 +2,7 @@ .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title #{title} .modal-body = form_tag form_path, method: method, class: 'js-quick-submit js-upload-blob-form form-horizontal' do @@ -12,7 +12,7 @@ Attach a file by drag & drop or = link_to 'click to upload', '#', class: "markdown-selector" %br - .dropzone-alerts{class: "alert alert-danger data", style: "display:none"} + .dropzone-alerts.alert.alert-danger.data{ style: "display:none" } = render 'shared/new_commit_form', placeholder: placeholder diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index a79ae53c780..538f8591f13 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -13,15 +13,15 @@ - case diff_view - when :inline %td.old_line.diff-line-num{ data: { linenumber: line_old } } - %a{href: "##{line_old}", data: { linenumber: line_old }} + %a{ href: "##{line_old}", data: { linenumber: line_old } } %td.new_line.diff-line-num{ data: { linenumber: line_new } } - %a{href: "##{line_new}", data: { linenumber: line_new }} + %a{ href: "##{line_new}", data: { linenumber: line_new } } = line_content - when :parallel - %td.old_line.diff-line-num{data: { linenumber: line_old }} + %td.old_line.diff-line-num{ data: { linenumber: line_old } } = link_to raw(line_old), "##{line_old}" = line_content - %td.new_line.diff-line-num{data: { linenumber: line_new }} + %td.new_line.diff-line-num{ data: { linenumber: line_new } } = link_to raw(line_new), "##{line_new}" = line_content diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml index 541dc96c45f..5cafb644b40 100644 --- a/app/views/projects/blob/preview.html.haml +++ b/app/views/projects/blob/preview.html.haml @@ -20,6 +20,6 @@ - else %td.old_line.diff-line-num %td.new_line.diff-line-num - %td.line_content{class: "#{line.type}"}= diff_line_content(line.text) + %td.line_content{ class: "#{line.type}" }= diff_line_content(line.text) - else .nothing-here-block No changes. diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index 0ab78a39cf9..b6738c3380f 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -5,7 +5,7 @@ %div{ class: container_class } = render 'projects/last_push' - %div#tree-holder.tree-holder + #tree-holder.tree-holder = render 'blob', blob: @blob - if can_edit_blob?(@blob) diff --git a/app/views/projects/boards/components/_card.html.haml b/app/views/projects/boards/components/_card.html.haml index 1f31496e73f..e4c2aff46ec 100644 --- a/app/views/projects/boards/components/_card.html.haml +++ b/app/views/projects/boards/components/_card.html.haml @@ -17,7 +17,7 @@ ":title" => '"Assigned to " + issue.assignee.name', "v-if" => "issue.assignee", data: { container: 'body' } } - %img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20 } + %img.avatar.avatar-inline.s20{ ":src" => "issue.assignee.avatar", width: 20, height: 20, alt: "Avatar" } %button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels", type: "button", "v-if" => "(!list.label || label.id !== list.label.id)", diff --git a/app/views/projects/boards/components/sidebar/_assignee.html.haml b/app/views/projects/boards/components/sidebar/_assignee.html.haml index 8fe1b832071..e75ce305440 100644 --- a/app/views/projects/boards/components/sidebar/_assignee.html.haml +++ b/app/views/projects/boards/components/sidebar/_assignee.html.haml @@ -14,7 +14,7 @@ %a.author_link.bold{ ":href" => "'#{root_url}' + issue.assignee.username", "v-if" => "issue.assignee" } %img.avatar.avatar-inline.s32{ ":src" => "issue.assignee.avatar", - width: "32" } + width: "32", alt: "Avatar" } %span.author {{ issue.assignee.name }} %span.username diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 9135cee8364..2eb49685f08 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -4,7 +4,7 @@ - number_commits_behind = diverging_commit_counts[:behind] - number_commits_ahead = diverging_commit_counts[:ahead] - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) -%li(class="js-branch-#{branch.name}") +%li{ class: "js-branch-#{branch.name}" } %div = link_to namespace_project_tree_path(@project.namespace, @project, branch.name), class: 'item-title str-truncated' do = branch.name @@ -12,7 +12,7 @@ - if branch.name == @repository.root_ref %span.label.label-primary default - elsif @repository.merged_to_root_ref? branch.name - %span.label.label-info.has-tooltip(title="Merged into #{@repository.root_ref}") + %span.label.label-info.has-tooltip{ title: "Merged into #{@repository.root_ref}" } merged - if @project.protected_branch? branch.name diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 5fd664c7a93..ecd812312c0 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,7 +3,7 @@ = render "projects/commits/head" %div{ class: container_class } - .top-area + .top-area.adjust .nav-text Protected branches can be managed in project settings @@ -12,7 +12,7 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by branch name', id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } .dropdown.inline - %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light = projects_sort_options_hash[@sort] = icon('chevron-down') diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 5a6c8c243fa..e63bdb38bd8 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -2,7 +2,7 @@ - if @error .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + %button.close{ type: "button", "data-dismiss" => "alert" } × = @error %h3.page-title New Branch diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index ce8b66b1945..0b3adcbe121 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -114,7 +114,7 @@ - if @build.pipeline.stages_count > 1 .dropdown.build-dropdown .title Stage - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.stage-selection More = icon('chevron-down') %ul.dropdown-menu @@ -125,10 +125,10 @@ .builds-container - HasStatus::ORDERED_STATUSES.each do |build_status| - builds.select{|build| build.status == build_status}.each do |build| - .build-job{class: sidebar_build_class(build, @build), data: {stage: build.stage}} + .build-job{ class: sidebar_build_class(build, @build), data: { stage: build.stage } } = link_to namespace_project_build_path(@project.namespace, @project, build) do = icon('arrow-right') - %span{class: "ci-status-icon-#{build.status}"} + %span{ class: "ci-status-icon-#{build.status}" } = ci_icon_for_status(build.status) %span - if build.name @@ -136,4 +136,4 @@ - else = build.id - if build.retried? - %i.fa.fa-refresh.has-tooltip{data: { container: 'body', placement: 'bottom' }, title: 'Build was retried'} + %i.fa.fa-refresh.has-tooltip{ data: { container: 'body', placement: 'bottom' }, title: 'Build was retried' } diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 06070f12bbd..c623e39b21f 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -19,5 +19,5 @@ = link_to ci_lint_path, class: 'btn btn-default' do %span CI Lint - %div.content-list.builds-content-list + .content-list.builds-content-list = render "table", builds: @builds, project: @project diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index c69c53b656f..54724ef5cab 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -56,10 +56,10 @@ - else #js-build-scroll.scroll-controls .scroll-step - %a{ href: '#up-build-trace', id: 'scroll-top', class: 'scroll-link scroll-top', title: 'Scroll to top' } + %a.scroll-link.scroll-top{ href: '#up-build-trace', id: 'scroll-top', title: 'Scroll to top' } = custom_icon('scroll_up') = custom_icon('scroll_up_hover_active') - %a{ href: '#down-build-trace', id: 'scroll-bottom', class: 'scroll-link scroll-bottom', title: 'Scroll to bottom' } + %a.scroll-link.scroll-bottom{ href: '#down-build-trace', id: 'scroll-bottom', title: 'Scroll to bottom' } = custom_icon('scroll_down') = custom_icon('scroll_down_hover_active') - if @build.active? diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index f5659be25f0..67de8699b2e 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,6 +1,6 @@ - if current_user .project-action-button.dropdown.inline - %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} + %a.btn.dropdown-toggle{ href: '#', "data-toggle" => "dropdown" } = icon('plus') = icon("caret-down") %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 27da86b9efe..851fe44a86d 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -8,7 +8,7 @@ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: 'Fork project', class: 'btn' do = custom_icon('icon_fork') %span Fork - %div.count-with-arrow + .count-with-arrow %span.arrow = link_to namespace_project_forks_path(@project.namespace, @project), title: 'Forks', class: 'count' do = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 12d35101770..d57eb2cbfbc 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -6,7 +6,7 @@ - else = icon('star-o') %span Star - %div.count-with-arrow + .count-with-arrow %span.arrow %span.count.star-count = @project.star_count @@ -15,7 +15,7 @@ = link_to new_user_session_path, class: 'btn has-tooltip star-btn', title: 'You must sign in to star a project' do = icon('star') Star - %div.count-with-arrow + .count-with-arrow %span.arrow %span.count = @project.star_count diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index f1cb0201032..520113639b7 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -7,7 +7,7 @@ - coverage = local_assigns.fetch(:coverage, false) - allow_retry = local_assigns.fetch(:allow_retry, false) -%tr.build.commit{class: ('retried' if retried)} +%tr.build.commit{ class: ('retried' if retried) } %td.status = render "ci/status/badge", status: build.detailed_status(current_user) diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 2f8f153f9a9..e67492a36d1 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -49,7 +49,7 @@ .stage-container.mini-pipeline-graph .dropdown.inline.build-content - %button.has-tooltip.builds-dropdown.js-builds-dropdown-button{ type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name)}} + %button.has-tooltip.builds-dropdown.js-builds-dropdown-button{ type: 'button', data: { toggle: 'dropdown', title: "#{stage.name}: #{detailed_status.label}", placement: 'top', "stage-endpoint" => stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: stage.name) } } %span.has-tooltip{ class: status_klass } %span.mini-pipeline-graph-icon-container %span{ class: status_klass }= custom_icon(icon_status) @@ -81,7 +81,7 @@ .btn-group.inline - if actions.any? .btn-group - %a.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{type: 'button', 'data-toggle' => 'dropdown'} + %a.dropdown-toggle.btn.btn-default.js-pipeline-dropdown-manual-actions{ type: 'button', 'data-toggle' => 'dropdown' } = custom_icon('icon_play') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right @@ -92,7 +92,7 @@ %span= build.name.humanize - if artifacts.present? .btn-group - %a.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{type: 'button', 'data-toggle' => 'dropdown'} + %a.dropdown-toggle.btn.btn-default.build-artifacts.js-pipeline-dropdown-download{ type: 'button', 'data-toggle' => 'dropdown' } = icon("download") = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml index 782f558e8b0..12e4280d344 100644 --- a/app/views/projects/commit/_change.html.haml +++ b/app/views/projects/commit/_change.html.haml @@ -6,11 +6,11 @@ - label = 'Cherry-pick' - target_label = 'Pick into branch' -.modal{id: "modal-#{type}-commit"} +.modal{ id: "modal-#{type}-commit" } .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title== #{label} this #{commit.change_type_title(current_user)} .modal-body = form_tag send("#{type.underscore}_namespace_project_commit_path", @project.namespace, @project, commit.id), method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do @@ -18,7 +18,7 @@ = label_tag 'target_branch', target_label, class: 'control-label' .col-sm-10 = hidden_field_tag :target_branch, @project.default_branch, id: 'target_branch' - = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false }}) + = dropdown_tag(@project.default_branch, options: { title: "Switch branch", filter: true, placeholder: "Search branches", toggle_class: 'js-project-refs-dropdown js-target-branch dynamic', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "target_branch", selected: @project.default_branch, target_branch: @project.default_branch, refs_url: namespace_project_branches_path(@project.namespace, @project), submit_form_on_click: false } }) - if can?(current_user, :push_code, @project) .js-create-merge-request-container diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index c08ed8f6c16..a9ee9230076 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -63,7 +63,7 @@ - if @commit.status .well-segment.pipeline-info - %div{class: "icon-container ci-status-icon-#{@commit.status}"} + %div{ class: "icon-container ci-status-icon-#{@commit.status}" } = ci_icon_for_status(@commit.status) Pipeline = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace" diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index b8c64d1f13e..7afd3d80ef5 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -8,7 +8,7 @@ - if @commit.status = render "ci_menu" - else - %div.block-connector + .block-connector = render "projects/diffs/diffs", diffs: @diffs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml index ce416caa494..6f5835cb9be 100644 --- a/app/views/projects/commits/_commit_list.html.haml +++ b/app/views/projects/commits/_commit_list.html.haml @@ -1,7 +1,7 @@ - commits, hidden = limited_commits(@commits) - commits = Commit.decorate(commits, @project) -%div.panel.panel-default +.panel.panel-default .panel-heading Commits (#{@commits.count}) - if hidden > 0 diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 9628cbd1634..e77f23c7fd8 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -28,12 +28,12 @@ = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input input-short', spellcheck: false } - if current_user && current_user.private_token .control - = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, { format: :atom, private_token: current_user.private_token }), title: "Commits Feed", class: 'btn' do = icon("rss") %ul.breadcrumb.repo-breadcrumb = commits_breadcrumbs - %div{id: dom_id(@project)} + %div{ id: dom_id(@project) } %ol#commits-list.list-unstyled.content_list = render 'commits', project: @project, ref: @ref = spinner diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index ef1b38d5e21..89e2e162b5b 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -7,7 +7,7 @@ #cycle-analytics{ class: container_class, "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - if @cycle_analytics_no_data - .bordered-box.landing.content-block{"v-if" => "!isOverviewDialogDismissed"} + .bordered-box.landing.content-block{ "v-if" => "!isOverviewDialogDismissed" } = icon("times", class: "dismiss-icon", "@click" => "dismissOverviewDialog()") .row .col-sm-3.col-xs-12.svg-container @@ -20,19 +20,19 @@ = link_to "Read more", help_page_path('user/project/cycle_analytics'), target: '_blank', class: 'btn' = icon("spinner spin", "v-show" => "isLoading") - .wrapper{"v-show" => "!isLoading && !hasError"} + .wrapper{ "v-show" => "!isLoading && !hasError" } .panel.panel-default .panel-heading Pipeline Health .content-block .container-fluid .row - .col-sm-3.col-xs-12.column{"v-for" => "item in state.summary"} + .col-sm-3.col-xs-12.column{ "v-for" => "item in state.summary" } %h3.header {{item.value}} %p.text {{item.title}} .col-sm-3.col-xs-12.column .dropdown.inline.js-ca-dropdown - %button.dropdown-menu-toggle{"data-toggle" => "dropdown", :type => "button"} + %button.dropdown-menu-toggle{ "data-toggle" => "dropdown", :type => "button" } %span.dropdown-label Last 30 days %i.fa.fa-chevron-down %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 450aaeb367c..d1e3cb14022 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -6,6 +6,9 @@ = deploy_key.title .description = deploy_key.fingerprint + - if deploy_key.can_push? + .write-access-allowed + Write access allowed .deploy-key-content.prepend-left-default.deploy-key-projects - deploy_key.projects.each do |project| - if can?(current_user, :read_project, project) diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 901605f7ca3..c91bb9c255a 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -10,4 +10,13 @@ %p.light.append-bottom-0 Paste a machine public key here. Read more about how to generate it = link_to "here", help_page_path("ssh/README") + .form-group + .checkbox + = f.label :can_push do + = f.check_box :can_push + %strong Write access allowed + .form-group + %p.light.append-bottom-0 + Allow this key to push to repository as well? (Default only allows pull access.) + = f.submit "Add key", class: "btn-create btn" diff --git a/app/views/projects/deployments/_actions.haml b/app/views/projects/deployments/_actions.haml index 58a214bdbd1..a680b1ca017 100644 --- a/app/views/projects/deployments/_actions.haml +++ b/app/views/projects/deployments/_actions.haml @@ -3,7 +3,7 @@ - if actions.present? .inline .dropdown - %a.dropdown-new.btn.btn-default{type: 'button', 'data-toggle' => 'dropdown'} + %a.dropdown-new.btn.btn-default{ type: 'button', 'data-toggle' => 'dropdown' } = custom_icon('icon_play') = icon('caret-down') %ul.dropdown-menu.dropdown-menu-align-right diff --git a/app/views/projects/deployments/_commit.html.haml b/app/views/projects/deployments/_commit.html.haml index ff250eeca50..170d786ecbf 100644 --- a/app/views/projects/deployments/_commit.html.haml +++ b/app/views/projects/deployments/_commit.html.haml @@ -1,4 +1,4 @@ -%div.branch-commit +.branch-commit - if deployment.ref .icon-container = deployment.tag? ? icon('tag') : icon('code-fork') diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index 6120b2191dd..52a1ece7d60 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -10,7 +10,7 @@ .nothing-here-block This diff was suppressed by a .gitattributes entry. - elsif diff_file.collapsed? - url = url_for(params.merge(action: :diff_for_path, old_path: diff_file.old_path, new_path: diff_file.new_path, file_identifier: diff_file.file_identifier)) - .nothing-here-block.diff-collapsed{data: { diff_for_path: url } } + .nothing-here-block.diff-collapsed{ data: { diff_for_path: url } } This diff is collapsed. %a.click-to-expand Click to expand it. diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 6c33d80becd..15df2edefc7 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,5 +1,5 @@ -.diff-file.file-holder{id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id)} - .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} +.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_commit.id) } + .file-title{ id: "file-path-#{hexdigest(diff_file.file_path)}" } = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "##{file_hash}" - unless diff_file.submodule? diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index d3ed8e1bf38..90c9a0c6c2b 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -21,7 +21,7 @@ - if diff_file.deleted_file deleted - = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy filename to clipboard') + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-clipboard btn-transparent prepend-left-5', title: 'Copy file path to clipboard') - if diff_file.mode_changed? %small diff --git a/app/views/projects/diffs/_image.html.haml b/app/views/projects/diffs/_image.html.haml index 9ec6a7aa5cd..1bccaaf5273 100644 --- a/app/views/projects/diffs/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -7,16 +7,16 @@ - if diff.renamed_file || diff.new_file || diff.deleted_file .image %span.wrap - .frame{class: image_diff_class(diff)} - %img{src: diff.deleted_file ? old_file_raw_path : file_raw_path} + .frame{ class: image_diff_class(diff) } + %img{ src: diff.deleted_file ? old_file_raw_path : file_raw_path, alt: diff.new_path } %p.image-info= "#{number_to_human_size file.size}" - else .image - %div.two-up.view + .two-up.view %span.wrap .frame.deleted - %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path))} - %img{src: old_file_raw_path} + %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.old_ref, diff.old_path)) } + %img{ src: old_file_raw_path, alt: diff.old_path } %p.image-info.hide %span.meta-filesize= "#{number_to_human_size old_file.size}" | @@ -27,8 +27,8 @@ %span.meta-height %span.wrap .frame.added - %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path))} - %img{src: file_raw_path} + %a{ href: namespace_project_blob_path(@project.namespace, @project, tree_join(diff_file.new_ref, diff.new_path)) } + %img{ src: file_raw_path, alt: diff.new_path } %p.image-info.hide %span.meta-filesize= "#{number_to_human_size file.size}" | @@ -38,32 +38,32 @@ %b H: %span.meta-height - %div.swipe.view.hide + .swipe.view.hide .swipe-frame .frame.deleted - %img{src: old_file_raw_path} + %img{ src: old_file_raw_path, alt: diff.old_path } .swipe-wrap .frame.added - %img{src: file_raw_path} + %img{ src: file_raw_path, alt: diff.new_path } %span.swipe-bar %span.top-handle %span.bottom-handle - %div.onion-skin.view.hide + .onion-skin.view.hide .onion-skin-frame .frame.deleted - %img{src: old_file_raw_path} + %img{ src: old_file_raw_path, alt: diff.old_path } .frame.added - %img{src: file_raw_path} + %img{ src: file_raw_path, alt: diff.new_path } .controls .transparent .drag-track - .dragger{:style => "left: 0px;"} + .dragger{ :style => "left: 0px;" } .opaque .view-modes.hide %ul.view-modes-menu - %li.two-up{data: {mode: 'two-up'}} 2-up - %li.swipe{data: {mode: 'swipe'}} Swipe - %li.onion-skin{data: {mode: 'onion-skin'}} Onion skin + %li.two-up{ data: { mode: 'two-up' } } 2-up + %li.swipe{ data: { mode: 'swipe' } } Swipe + %li.onion-skin{ data: { mode: 'onion-skin' } } Onion skin diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 16c96b66714..cd18ba2ed00 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -16,13 +16,13 @@ - if plain = link_text - else - %a{href: "##{line_code}", data: { linenumber: link_text }} + %a{ href: "##{line_code}", data: { linenumber: link_text } } %td.new_line.diff-line-num{ class: type, data: { linenumber: line.new_pos } } - link_text = type == "old" ? " " : line.new_pos - if plain = link_text - else - %a{href: "##{line_code}", data: { linenumber: link_text }} + %a{ href: "##{line_code}", data: { linenumber: link_text } } %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }< - if email %pre= line.text diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 78aa9fb7391..b087485aa17 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,5 +1,5 @@ / Side-by-side diff view -%div.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } +.text-file.diff-wrap-lines.code.js-syntax-highlight{ data: diff_view_data } %table - last_line = 0 - diff_file.parallel_diff_lines.each do |line| @@ -13,9 +13,9 @@ - else - left_line_code = diff_file.line_code(left) - left_position = diff_file.position(left) - %td.old_line.diff-line-num{id: left_line_code, class: left.type, data: { linenumber: left.old_pos }} - %a{href: "##{left_line_code}" }= raw(left.old_pos) - %td.line_content.parallel.noteable_line{class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old')}= diff_line_content(left.text) + %td.old_line.diff-line-num{ id: left_line_code, class: left.type, data: { linenumber: left.old_pos } } + %a{ href: "##{left_line_code}" }= raw(left.old_pos) + %td.line_content.parallel.noteable_line{ class: left.type, data: diff_view_line_data(left_line_code, left_position, 'old') }= diff_line_content(left.text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel @@ -26,9 +26,9 @@ - else - right_line_code = diff_file.line_code(right) - right_position = diff_file.position(right) - %td.new_line.diff-line-num{id: right_line_code, class: right.type, data: { linenumber: right.new_pos }} - %a{href: "##{right_line_code}" }= raw(right.new_pos) - %td.line_content.parallel.noteable_line{class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new')}= diff_line_content(right.text) + %td.new_line.diff-line-num{ id: right_line_code, class: right.type, data: { linenumber: right.new_pos } } + %a{ href: "##{right_line_code}" }= raw(right.new_pos) + %td.line_content.parallel.noteable_line{ class: right.type, data: diff_view_line_data(right_line_code, right_position, 'new') }= diff_line_content(right.text) - else %td.old_line.diff-line-num.empty-cell %td.line_content.parallel diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index 66d6254aa1e..290f696d582 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -14,24 +14,23 @@ %li - if diff_file.deleted_file %span.deleted-file - %a{href: "##{file_hash}"} + %a{ href: "##{file_hash}" } %i.fa.fa-minus = diff_file.old_path - elsif diff_file.renamed_file %span.renamed-file - %a{href: "##{file_hash}"} + %a{ href: "##{file_hash}" } %i.fa.fa-minus = diff_file.old_path → = diff_file.new_path - elsif diff_file.new_file %span.new-file - %a{href: "##{file_hash}"} + %a{ href: "##{file_hash}" } %i.fa.fa-plus = diff_file.new_path - else %span.edit-file - %a{href: "##{file_hash}"} + %a{ href: "##{file_hash}" } %i.fa.fa-adjust = diff_file.new_path - diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index c0a83091c8c..3525a07a687 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -32,7 +32,7 @@ .empty_wrapper %h3.page-title-empty Command line instructions - %div.git-empty + .git-empty %fieldset %h5 Git global setup %pre.light-well diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml index 0c6f696f5b9..8c728eb0f6a 100644 --- a/app/views/projects/environments/index.html.haml +++ b/app/views/projects/environments/index.html.haml @@ -16,4 +16,4 @@ "css-class" => container_class, "commit-icon-svg" => custom_icon("icon_commit"), "terminal-icon-svg" => custom_icon("icon_terminal"), - "play-icon-svg" => custom_icon("icon_play")}} + "play-icon-svg" => custom_icon("icon_play") } } diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index a6726e509e0..431253c1299 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -6,7 +6,7 @@ = stylesheet_link_tag "xterm/xterm" = page_specific_javascript_tag("terminal/terminal_bundle.js") -%div{class: container_class} +%div{ class: container_class } .top-area .row .col-sm-6 @@ -18,5 +18,5 @@ .nav-controls = render 'projects/deployments/actions', deployment: @environment.last_deployment -.terminal-container{class: container_class} - #terminal{data:{project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws"}} +.terminal-container{ class: container_class } + #terminal{ data: { project_path: "#{terminal_namespace_project_environment_path(@project.namespace, @project, @environment)}.ws" } } diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml index 9322c82904f..4cdb44325b3 100644 --- a/app/views/projects/find_file/show.html.haml +++ b/app/views/projects/find_file/show.html.haml @@ -9,11 +9,11 @@ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do = @project.path %li.file-finder - %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path', autocomplete: 'off'} + %input#file_find.form-control.file-finder-input{ type: "text", placeholder: 'Find by path', autocomplete: 'off' } - %div.tree-content-holder + .tree-content-holder .table-holder - %table.table.files-slider{class: "table_#{@hex_path} tree-table table-striped" } + %table.table.files-slider{ class: "table_#{@hex_path} tree-table table-striped" } %tbody = spinner nil, true diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index 5ee3979c7e7..6c8a6f051a9 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -1,7 +1,7 @@ .top-area .nav-text - full_count_title = "#{@public_forks_count} public and #{@private_forks_count} private" - == #{pluralize(@total_forks_count, 'fork')}: #{full_count_title} + = "#{pluralize(@total_forks_count, 'fork')}: #{full_count_title}" .nav-controls = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| @@ -9,7 +9,7 @@ spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } .dropdown - %button.dropdown-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span.light sort: - if @sort.present? = sort_options_hash[@sort] diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 9f444f076c0..6d7af1685fd 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -6,7 +6,7 @@ - stage = local_assigns.fetch(:stage, false) - coverage = local_assigns.fetch(:coverage, false) -%tr.generic_commit_status{class: ('retried' if retried)} +%tr.generic_commit_status{ class: ('retried' if retried) } %td.status = render 'ci/status/badge', status: generic_commit_status.detailed_status(current_user) diff --git a/app/views/projects/graphs/ci/_build_times.haml b/app/views/projects/graphs/ci/_build_times.haml index 195f18afc76..bb0975a9535 100644 --- a/app/views/projects/graphs/ci/_build_times.haml +++ b/app/views/projects/graphs/ci/_build_times.haml @@ -2,7 +2,7 @@ %p.light Commit duration in minutes for last 30 commits - %canvas#build_timesChart{height: 200} + %canvas#build_timesChart{ height: 200 } :javascript var data = { diff --git a/app/views/projects/graphs/ci/_builds.haml b/app/views/projects/graphs/ci/_builds.haml index 1fbf6ca2c1c..431657c4dcb 100644 --- a/app/views/projects/graphs/ci/_builds.haml +++ b/app/views/projects/graphs/ci/_builds.haml @@ -13,18 +13,18 @@ %p.light Builds for last week (#{date_from_to(Date.today - 7.days, Date.today)}) - %canvas#weekChart{height: 200} + %canvas#weekChart{ height: 200 } .prepend-top-default %p.light Builds for last month (#{date_from_to(Date.today - 30.days, Date.today)}) - %canvas#monthChart{height: 200} + %canvas#monthChart{ height: 200 } .prepend-top-default %p.light Builds for last year - %canvas#yearChart.padded{height: 250} + %canvas#yearChart.padded{ height: 250 } - [:week, :month, :year].each do |scope| :javascript diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index ac5f792d140..5ebb939a109 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -21,7 +21,7 @@ %h3#date_header.page-title %p.light Commits to #{@ref}, excluding merge commits. Limited to 6,000 commits. - %input#brush_change{:type => "hidden"} + %input#brush_change{ :type => "hidden" } .graphs.row #contributors-master #contributors.clearfix diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index c80210d6ff4..bd46af339cf 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -43,7 +43,7 @@ = icon('clock-o') = issue.milestone.title - if issue.due_date - %span{class: "#{'cred' if issue.overdue?}"} + %span{ class: "#{'cred' if issue.overdue?}" } = icon('calendar') = issue.due_date.to_s(:medium) diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml index c56b6cc11f5..13e2150f997 100644 --- a/app/views/projects/issues/_new_branch.html.haml +++ b/app/views/projects/issues/_new_branch.html.haml @@ -1,9 +1,6 @@ - if can?(current_user, :push_code, @project) .pull-right - #new-branch.new-branch{'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue)} - = link_to '#', class: 'checking btn btn-grouped', disabled: 'disabled' do - = icon('spinner spin') - Checking branches + #new-branch.new-branch{ 'data-path' => can_create_branch_namespace_project_issue_path(@project.namespace, @project, @issue) } = link_to namespace_project_branches_path(@project.namespace, @project, branch_name: @issue.to_branch_name, issue_iid: @issue.iid), method: :post, class: 'btn btn-new btn-inverted btn-grouped has-tooltip available hide', title: @issue.to_branch_name do New branch diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index 7980f7c9a72..24e86b8497f 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -8,7 +8,9 @@ = @teams.one? ? 'The team' : 'Select the team' where the slash commands will be used in - selected_id = @teams.keys.first if @teams.one? - = f.select(:team_id, mattermost_teams_options(@teams), {}, { class: 'form-control', selected: "#{selected_id}", disabled: @teams.one? }) + - options = mattermost_teams_options(@teams) + - options = options_for_select(options, selected_id) + = f.select(:team_id, options, {}, { class: 'form-control', selected: "#{selected_id}" }) .help-block - if @teams.one? This is the only team where you are an administrator. diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index b3c43286a50..e3b0aa7e644 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -65,7 +65,7 @@ - merge_request.labels.each do |label| = link_to_label(label, subject: merge_request.project, type: :merge_request) - + - if merge_request.tasks? %span.task-status diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 349181be784..34ead6427e0 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -10,7 +10,7 @@ %span.pull-right = link_to 'Change branches', mr_change_branches_path(@merge_request) %hr -= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal common-note-form js-requires-input js-quick-submit' } do |f| = render 'shared/issuable/form', f: f, issuable: @merge_request = f.hidden_field :source_project_id = f.hidden_field :source_branch diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index d1fa51ae7ee..633701c6f40 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -5,10 +5,10 @@ - content_for :page_specific_javascripts do = page_specific_javascript_tag('diff_notes/diff_notes_bundle.js') -.merge-request{'data-url' => merge_request_path(@merge_request)} +.merge-request{ 'data-url' => merge_request_path(@merge_request) } = render "projects/merge_requests/show/mr_title" - .merge-request-details.issuable-details{data: {id: @merge_request.project.id}} + .merge-request-details.issuable-details{ data: { id: @merge_request.project.id } } = render "projects/merge_requests/show/mr_box" .append-bottom-default.mr-source-target.prepend-top-default - if @merge_request.open? @@ -47,6 +47,9 @@ = succeed '.' do = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" + .content-block.content-block-small + = render 'award_emoji/awards_block', awardable: @merge_request, inline: true + - if @commits_count.nonzero? .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } %div{ class: container_class } @@ -70,7 +73,7 @@ Changes %span.badge= @merge_request.diff_size %li#resolve-count-app.line-resolve-all-container.pull-right.prepend-top-10.hidden-xs{ "v-cloak" => true } - %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } + %resolve-count{ "inline-template" => true, ":logged-out" => "#{current_user.nil?}" } %div .line-resolve-all{ "v-show" => "discussionCount > 0", ":class" => "{ 'has-next-btn': !loggedOut && resolvedDiscussionCount !== discussionCount }" } @@ -83,9 +86,6 @@ .tab-content#diff-notes-app #notes.notes.tab-pane.voting_notes - .content-block.content-block-small - = render 'award_emoji/awards_block', awardable: @merge_request, inline: true - .row %section.col-md-12 .issuable-discussion diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml index 16789f68f70..b8b87dcdcaf 100644 --- a/app/views/projects/merge_requests/conflicts.html.haml +++ b/app/views/projects/merge_requests/conflicts.html.haml @@ -9,29 +9,29 @@ = render 'shared/issuable/sidebar', issuable: @merge_request -#conflicts{"v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json), +#conflicts{ "v-cloak" => "true", data: { conflicts_path: conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request, format: :json), resolve_conflicts_path: resolve_conflicts_namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request) } } - .loading{"v-if" => "isLoading"} + .loading{ "v-if" => "isLoading" } %i.fa.fa-spinner.fa-spin - .nothing-here-block{"v-if" => "hasError"} + .nothing-here-block{ "v-if" => "hasError" } {{conflictsData.errorMessage}} = render partial: "projects/merge_requests/conflicts/commit_stats" - .files-wrapper{"v-if" => "!isLoading && !hasError"} + .files-wrapper{ "v-if" => "!isLoading && !hasError" } .files - .diff-file.file-holder.conflict{"v-for" => "file in conflictsData.files"} + .diff-file.file-holder.conflict{ "v-for" => "file in conflictsData.files" } .file-title - %i.fa.fa-fw{":class" => "file.iconClass"} + %i.fa.fa-fw{ ":class" => "file.iconClass" } %strong {{file.filePath}} = render partial: 'projects/merge_requests/conflicts/file_actions' .diff-content.diff-wrap-lines - .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "!isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } = render partial: "projects/merge_requests/conflicts/components/inline_conflict_lines" - .diff-wrap-lines.code.file-content.js-syntax-highlight{"v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } + .diff-wrap-lines.code.file-content.js-syntax-highlight{ "v-show" => "isParallel && file.resolveMode === 'interactive' && file.type === 'text'" } %parallel-conflict-lines{ ":file" => "file" } - %div{"v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'"} + %div{ "v-show" => "file.resolveMode === 'edit' || file.type === 'text-editor'" } = render partial: "projects/merge_requests/conflicts/components/diff_file_editor" = render partial: "projects/merge_requests/conflicts/submit_form" diff --git a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml index 5ab3cd96163..964dc40a213 100644 --- a/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml +++ b/app/views/projects/merge_requests/conflicts/_commit_stats.html.haml @@ -1,9 +1,9 @@ -.content-block.oneline-block.files-changed{"v-if" => "!isLoading && !hasError"} - .inline-parallel-buttons{"v-if" => "showDiffViewTypeSwitcher"} +.content-block.oneline-block.files-changed{ "v-if" => "!isLoading && !hasError" } + .inline-parallel-buttons{ "v-if" => "showDiffViewTypeSwitcher" } .btn-group - %button.btn{":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')"} + %button.btn{ ":class" => "{'active': !isParallel}", "@click" => "handleViewTypeChange('inline')" } Inline - %button.btn{":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')"} + %button.btn{ ":class" => "{'active': isParallel}", "@click" => "handleViewTypeChange('parallel')" } Side-by-side .js-toggle-container diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml index 05af57acf03..2595ce74ac0 100644 --- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml +++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml @@ -1,5 +1,5 @@ .file-actions - .btn-group{"v-if" => "file.type === 'text'"} + .btn-group{ "v-if" => "file.type === 'text'" } %button.btn{ ":class" => "{ 'active': file.resolveMode == 'interactive' }", '@click' => "onClickResolveModeButton(file, 'interactive')", type: 'button' } @@ -8,5 +8,5 @@ '@click' => "onClickResolveModeButton(file, 'edit')", type: 'button' } Edit inline - %a.btn.view-file.btn-file-option{":href" => "file.blobPath"} + %a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" } View file @{{conflictsData.shortCommitSha}} diff --git a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml index 6ffaa9ad4d2..62c9748c510 100644 --- a/app/views/projects/merge_requests/conflicts/_submit_form.html.haml +++ b/app/views/projects/merge_requests/conflicts/_submit_form.html.haml @@ -10,7 +10,7 @@ .col-sm-offset-2.col-sm-10 .row .col-xs-6 - %button{ type: "button", class: "btn btn-success js-submit-button", "@click" => "commit()", ":disabled" => "!readyToCommit" } + %button.btn.btn-success.js-submit-button{ type: "button", "@click" => "commit()", ":disabled" => "!readyToCommit" } %span {{commitButtonText}} .col-xs-6.text-right = link_to "Cancel", namespace_project_merge_request_path(@merge_request.project.namespace, @merge_request.project, @merge_request), class: "btn btn-cancel" diff --git a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml index 3c927d362c2..aff3fb82fa6 100644 --- a/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml @@ -1,4 +1,4 @@ -%diff-file-editor{"inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation"} +%diff-file-editor{ "inline-template" => "true", ":file" => "file", ":on-cancel-discard-confirmation" => "cancelDiscardConfirmation", ":on-accept-discard-confirmation" => "acceptDiscardConfirmation" } .diff-editor-wrap{ "v-show" => "file.showEditor" } .discard-changes-alert-wrap{ "v-if" => "file.promptDiscardConfirmation" } .discard-changes-alert diff --git a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml index d35c7bee163..d828cb6cf9e 100644 --- a/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml +++ b/app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml @@ -1,14 +1,14 @@ -%inline-conflict-lines{ "inline-template" => "true", ":file" => "file"} +%inline-conflict-lines{ "inline-template" => "true", ":file" => "file" } %table - %tr.line_holder.diff-inline{"v-for" => "line in file.inlineLines"} - %td.diff-line-num.new_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %tr.line_holder.diff-inline{ "v-for" => "line in file.inlineLines" } + %td.diff-line-num.new_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" } %a {{line.new_line}} - %td.diff-line-num.old_line{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader"} + %td.diff-line-num.old_line{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader" } %a {{line.old_line}} - %td.line_content{":class" => "lineCssClass(line)", "v-if" => "!line.isHeader", "v-html" => "line.richText"} - %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} - %td.diff-line-num.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} - %td.line_content.header{":class" => "lineCssClass(line)", "v-if" => "line.isHeader"} - %strong{"v-html" => "line.richText"} + %td.line_content{ ":class" => "lineCssClass(line)", "v-if" => "!line.isHeader", "v-html" => "line.richText" } + %td.diff-line-num.header{ ":class" => "lineCssClass(line)", "v-if" => "line.isHeader" } + %td.diff-line-num.header{ ":class" => "lineCssClass(line)", "v-if" => "line.isHeader" } + %td.line_content.header{ ":class" => "lineCssClass(line)", "v-if" => "line.isHeader" } + %strong{ "v-html" => "line.richText" } %button.btn{ "@click" => "handleSelected(file, line.id, line.section)" } {{line.buttonTitle}} diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index f1d5441f9dd..ec76c6a5417 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -1,8 +1,8 @@ -%div#modal_merge_info.modal +#modal_merge_info.modal .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3 Check out, review, and merge locally .modal-body %p diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml index ed23d06ee5e..683cb8a5a27 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -4,7 +4,7 @@ %div - if @merge_request.description.present? - .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''} + .description{ class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : '' } .wiki = preserve do = markdown_field(@merge_request, :description) diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 5cc92595fe0..ac4a03220b9 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -1,6 +1,6 @@ - if @merge_request_diffs.size > 1 .mr-version-controls - %div.mr-version-menus-container.content-block + .mr-version-menus-container.content-block Changes between %span.dropdown.inline.mr-version-dropdown %a.dropdown-toggle.btn.btn-default{ data: {toggle: :dropdown} } @@ -13,7 +13,7 @@ .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Version: - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times', class: 'dropdown-menu-close-icon') .dropdown-content %ul @@ -43,7 +43,7 @@ .dropdown-menu.dropdown-select.dropdown-menu-selectable .dropdown-title %span Compared with: - %button.dropdown-title-button.dropdown-menu-close{aria: {label: "Close"}} + %button.dropdown-title-button.dropdown-menu-close{ aria: { label: "Close" } } = icon('times', class: 'dropdown-menu-close-icon') .dropdown-content %ul diff --git a/app/views/projects/merge_requests/widget/_closed.html.haml b/app/views/projects/merge_requests/widget/_closed.html.haml index f3cc0e7e8a1..15f47ecf210 100644 --- a/app/views/projects/merge_requests/widget/_closed.html.haml +++ b/app/views/projects/merge_requests/widget/_closed.html.haml @@ -6,7 +6,7 @@ - if @merge_request.closed_event by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} - %p + %p = succeed '.' do The changes were not merged into %span.label-branch= @merge_request.target_branch diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 5bc417d1760..c80dc33058d 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -17,7 +17,7 @@ - # TODO, remove in later versions when services like Jenkins will set CI status via Commit status API .mr-widget-heading - %w[success skipped canceled failed running pending].each do |status| - .ci_widget{class: "ci-#{status} ci-status-icon-#{status}", style: "display:none"} + .ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: "display:none" } = ci_icon_for_status(status) %span CI build @@ -32,11 +32,11 @@ = icon("spinner spin") Checking CI status for #{@merge_request.diff_head_commit.short_id}… - .ci_widget.ci-not_found{style: "display:none"} + .ci_widget.ci-not_found{ style: "display:none" } = icon("times-circle") Could not find CI status for #{@merge_request.diff_head_commit.short_id}. - .ci_widget.ci-error{style: "display:none"} + .ci_widget.ci-error{ style: "display:none" } = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index f4aa1609a1b..c0d6ab669b8 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -42,6 +42,4 @@ - if mr_issues_mentioned_but_not_closing.present? #{"Issue".pluralize(mr_issues_mentioned_but_not_closing.size)} != markdown issues_sentence(mr_issues_mentioned_but_not_closing), pipeline: :gfm, author: @merge_request.author - #{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not closed. - - + #{mr_issues_mentioned_but_not_closing.size > 1 ? 'are' : 'is'} mentioned but will not be closed. diff --git a/app/views/projects/merge_requests/widget/open/_archived.html.haml b/app/views/projects/merge_requests/widget/open/_archived.html.haml index ab30fa6b243..0d61e56d8fb 100644 --- a/app/views/projects/merge_requests/widget/open/_archived.html.haml +++ b/app/views/projects/merge_requests/widget/open/_archived.html.haml @@ -1,4 +1,4 @@ -%h4 +%h4 Project is archived %p This merge request cannot be merged because archived projects cannot be written to. diff --git a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml index af3096f04d9..c98b2c42597 100644 --- a/app/views/projects/merge_requests/widget/open/_conflicts.html.haml +++ b/app/views/projects/merge_requests/widget/open/_conflicts.html.haml @@ -1,21 +1,23 @@ +- can_resolve = @merge_request.conflicts_can_be_resolved_by?(current_user) +- can_resolve_in_ui = @merge_request.conflicts_can_be_resolved_in_ui? +- can_merge = @merge_request.can_be_merged_via_command_line_by?(current_user) + %h4.has-conflicts = icon("exclamation-triangle") This merge request contains merge conflicts %p - Please - - if @merge_request.conflicts_can_be_resolved_by?(current_user) - - if @merge_request.conflicts_can_be_resolved_in_ui? - = link_to "resolve these conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) - - else - %span.has-tooltip{title: "These conflicts cannot be resolved through GitLab"} - resolve these conflicts locally - - else - resolve these conflicts - + To merge this request, resolve these conflicts + - if can_resolve && !can_resolve_in_ui + locally or + - unless can_merge + ask someone with write access to this repository to + merge it locally. - - if @merge_request.can_be_merged_via_command_line_by?(current_user) - #{link_to "merge this request manually", "#modal_merge_info", class: "how_to_merge_link vlink", "data-toggle" => "modal"}. - - else - ask someone with write access to this repository to merge this request manually. +- if (can_resolve && can_resolve_in_ui) || can_merge + .btn-group + - if can_resolve && can_resolve_in_ui + = link_to "Resolve conflicts", conflicts_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn" + - if can_merge + = link_to "Merge locally", "#modal_merge_info", class: "btn how_to_merge_link vlink", "data-toggle" => "modal" diff --git a/app/views/projects/merge_requests/widget/open/_nothing.html.haml b/app/views/projects/merge_requests/widget/open/_nothing.html.haml index 35626b624b7..7af8c01c134 100644 --- a/app/views/projects/merge_requests/widget/open/_nothing.html.haml +++ b/app/views/projects/merge_requests/widget/open/_nothing.html.haml @@ -1,4 +1,4 @@ -%h4 +%h4 = icon("exclamation-triangle") Nothing to merge from %span.label-branch= source_branch_with_namespace(@merge_request) diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 513710e8e66..0f4a8508751 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -20,6 +20,6 @@ - if @milestone.new_record? = f.submit 'Create milestone', class: "btn-create btn" = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel" - -else + - else = f.submit 'Save changes', class: "btn-save btn" = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel" diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 866b278ce57..064e92b15eb 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -77,7 +77,7 @@ - if git_import_enabled? = link_to "#", class: 'btn js-toggle-button import_git' do = icon('git', text: 'Repo by URL') - %div{ class: 'import_gitlab_project' } + .import_gitlab_project - if gitlab_project_import_enabled? = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit' do = icon('gitlab', text: 'GitLab export') diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 46b402545cd..39731668a61 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -23,5 +23,5 @@ .note-form-actions.clearfix = f.submit 'Comment', class: "btn btn-nr btn-create append-right-10 comment-btn js-comment-button" = yield(:note_actions) - %a.btn.btn-cancel.js-note-discard{role: "button", data: {cancel_text: "Cancel"}} + %a.btn.btn-cancel.js-note-discard{ role: "button", data: {cancel_text: "Cancel" } } Discard draft diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 399cf85cd0f..eb869ea85bf 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -5,7 +5,7 @@ %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } .timeline-entry-inner .timeline-icon - %a{href: user_path(note.author)} + %a{ href: user_path(note.author) } = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' .timeline-content .note-header @@ -15,7 +15,7 @@ - unless note.system commented - if note.system - %span{class: 'system-note-message'} + %span.system-note-message = note.redacted_note_html %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') @@ -61,7 +61,7 @@ = icon('pencil', class: 'link-highlight') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'note-action-button hidden-xs js-note-delete danger' do = icon('trash-o', class: 'danger-highlight') - .note-body{class: note_editable ? 'js-task-list-container' : ''} + .note-body{ class: note_editable ? 'js-task-list-container' : '' } .note-text.md = preserve do = note.redacted_note_html diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index b00ba2d5307..ca76f13ef5e 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -23,7 +23,7 @@ .info-well - if @commit.status .well-segment.pipeline-info - %div{class: "icon-container ci-status-icon-#{@commit.status}"} + %div{ class: "icon-container ci-status-icon-#{@commit.status}" } = ci_icon_for_status(@commit.status) = pluralize @pipeline.statuses.count(:id), "build" - if @pipeline.ref diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index e423fc8c471..b0ba7824514 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -5,23 +5,23 @@ %div{ class: container_class } .top-area %ul.nav-links - %li{class: ('active' if @scope.nil?)}> + %li{ class: ('active' if @scope.nil?) }> = link_to project_pipelines_path(@project) do All %span.badge.js-totalbuilds-count = number_with_delimiter(@pipelines_count) - %li{class: ('active' if @scope == 'running')}> + %li{ class: ('active' if @scope == 'running') }> = link_to project_pipelines_path(@project, scope: :running) do Running %span.badge.js-running-count = number_with_delimiter(@running_or_pending_count) - %li{class: ('active' if @scope == 'branches')}> + %li{ class: ('active' if @scope == 'branches') }> = link_to project_pipelines_path(@project, scope: :branches) do Branches - %li{class: ('active' if @scope == 'tags')}> + %li{ class: ('active' if @scope == 'tags') }> = link_to project_pipelines_path(@project, scope: :tags) do Tags diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml index 29a41bc664b..49c1d886423 100644 --- a/app/views/projects/pipelines/show.html.haml +++ b/app/views/projects/pipelines/show.html.haml @@ -2,7 +2,7 @@ - page_title "Pipeline" = render "projects/pipelines/head" -%div.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } +.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } } - if @commit = render "projects/pipelines/info" diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 6e58e5a0c78..7036b8a5ccc 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -1,4 +1,4 @@ -%li.runner{id: dom_id(runner)} +%li.runner{ id: dom_id(runner) } %h4 = runner_status_icon(runner) %span.monospace diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index a915c159cb4..80d4081dd7b 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -17,10 +17,10 @@ %ul.nav %li = link_to project_files_path(@project) do - Files (#{repository_size}) + Files (#{storage_counter(@project.statistics.total_repository_size)}) %li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do - #{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)}) + #{'Commit'.pluralize(@project.statistics.commit_count)} (#{number_with_delimiter(@project.statistics.commit_count)}) %li = link_to namespace_project_branches_path(@project.namespace, @project) do #{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)}) @@ -70,8 +70,8 @@ = link_to 'Set up Koding', add_koding_stack_path(@project) - if @repository.gitlab_ci_yml.blank? && @project.deployment_service.present? %li.missing - = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up autodeploy', target_branch: 'autodeploy', context: 'autodeploy') do - Set up autodeploy + = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml', commit_message: 'Set up auto deploy', target_branch: 'auto-deploy', context: 'autodeploy') do + Set up auto deploy - if @repository.commit .project-last-commit{ class: container_class } diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index 79d87b7db12..485b23815bc 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -15,4 +15,4 @@ .row-content-block.top-block.content-component-block = render 'award_emoji/awards_block', awardable: @snippet, inline: true - %div#notes= render "projects/notes/notes_with_form" + #notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index 6919b210a00..d9d392fa02f 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -10,11 +10,10 @@ - status_groups.each do |group_name, grouped_statuses| - if grouped_statuses.one? - status = grouped_statuses.first - %li.build{ 'id' => "ci-badge-#{group_name}" } + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve = render 'ci/status/graph_badge', subject: status - else - %li.build{ 'id' => "ci-badge-#{group_name}" } + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses - diff --git a/app/views/projects/stage/_in_stage_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index 65e5f31e86c..c4cb9ab50d0 100644 --- a/app/views/projects/stage/_in_stage_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml @@ -1,6 +1,6 @@ - group_status = CommitStatus.where(id: subject).status %button.dropdown-menu-toggle.build-content.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } - %span{class: "ci-status-icon ci-status-icon-#{group_status}"} + %span{ class: "ci-status-icon ci-status-icon-#{group_status}" } = ci_icon_for_status(group_status) %span.ci-status-text = name diff --git a/app/views/projects/stage/_stage.html.haml b/app/views/projects/stage/_stage.html.haml index 1684e02fbad..28e1c060875 100644 --- a/app/views/projects/stage/_stage.html.haml +++ b/app/views/projects/stage/_stage.html.haml @@ -1,13 +1,13 @@ %tr - %th{colspan: 10} + %th{ colspan: 10 } %strong %a{ name: stage.name } - %span{class: "ci-status-link ci-status-icon-#{stage.status}"} + %span{ class: "ci-status-link ci-status-icon-#{stage.status}" } = ci_icon_for_status(stage.status) = stage.name.titleize = render stage.statuses.latest_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, allow_retry: true = render stage.statuses.retried_ordered, coverage: @project.build_coverage_enabled?, stage: false, ref: false, pipeline_link: false, retried: true %tr - %td{colspan: 10} + %td{ colspan: 10 } diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index c42641afea0..8ef069b9e05 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,7 +1,7 @@ - commit = @repository.commit(tag.dereferenced_target) - release = @releases.find { |release| release.tag == tag.name } -%li - %div +%li.flex-row + .row-main-content.str-truncated = link_to namespace_project_tag_path(@project.namespace, @project, tag.name) do %span.item-title = icon('tag') @@ -10,24 +10,25 @@ = strip_gpg_signature(tag.message) - .controls - = render 'projects/buttons/download', project: @project, ref: tag.name + - if commit + .block-truncated + = render 'projects/branches/commit', commit: commit, project: @project + - else + %p + Cant find HEAD commit for this tag + - if release && release.description.present? + .description.prepend-top-default + .wiki + = preserve do + = markdown_field(release, :description) - - if can?(current_user, :push_code, @project) - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do - = icon("pencil") + .row-fixed-content.controls + = render 'projects/buttons/download', project: @project, ref: tag.name - - if can?(current_user, :admin_project, @project) - = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do - = icon("trash-o") + - if can?(current_user, :push_code, @project) + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, tag.name), class: 'btn has-tooltip', title: "Edit release notes", data: { container: "body" } do + = icon("pencil") - - if commit - = render 'projects/branches/commit', commit: commit, project: @project - - else - %p - Cant find HEAD commit for this tag - - if release && release.description.present? - .description.prepend-top-default - .wiki - = preserve do - = markdown_field(release, :description) + - if can?(current_user, :admin_project, @project) + = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{tag.name}' tag cannot be undone. Are you sure?", container: 'body' }, remote: true do + = icon("trash-o") diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 1d39f3a7534..e2f132f7742 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -2,16 +2,16 @@ - page_title "Tags" = render "projects/commits/head" -%div{ class: container_class } - .top-area - .nav-text +.flex-list{ class: container_class } + .top-area.flex-row + .nav-text.row-main-content Tags give the ability to mark specific points in history as being important - .nav-controls + .nav-controls.row-fixed-content = form_tag(filter_tags_path, method: :get) do = search_field_tag :search, params[:search], { placeholder: 'Filter by tag name', id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } - .dropdown.inline + .dropdown %button.dropdown-toggle{ type: 'button', data: { toggle: 'dropdown'} } %span.light = projects_sort_options_hash[@sort] @@ -30,7 +30,7 @@ .tags - if @tags.any? - %ul.content-list + %ul.flex-list.content-list = render partial: 'tag', collection: @tags = paginate @tags, theme: 'gitlab' diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index c06a413eb2f..160d4c7a223 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -2,7 +2,7 @@ - if @error .alert.alert-danger - %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + %button.close{ type: "button", "data-dismiss" => "alert" } × = @error %h3.page-title diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 12facb6eb73..fad3c5c2173 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -12,21 +12,23 @@ - else Cant find HEAD commit for this tag - .nav-controls + .nav-controls.controls-flex - if can?(current_user, :push_code, @project) - = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Edit release notes' do + = link_to edit_namespace_project_tag_release_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Edit release notes' do = icon("pencil") - = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse files' do + = link_to namespace_project_tree_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse files' do = icon('files-o') - = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn has-tooltip', title: 'Browse commits' do + = link_to namespace_project_commits_path(@project.namespace, @project, @tag.name), class: 'btn controls-item has-tooltip', title: 'Browse commits' do = icon('history') - = render 'projects/buttons/download', project: @project, ref: @tag.name + .btn-container.controls-item + = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_project, @project) - .pull-right + .btn-container.controls-item-full = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete tag", method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do %i.fa.fa-trash-o + - if @tag.message.present? - %pre.body + %pre.wrap = strip_gpg_signature(@tag.message) .append-bottom-default.prepend-top-default diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index ee417b58cbf..425b460eb09 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -6,4 +6,4 @@ %span.str-truncated= file_name %td.hidden-xs.tree-commit %td.tree-time-ago.cgray.text-right - = render 'projects/tree/spinner'
\ No newline at end of file + = render 'projects/tree/spinner' diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 2b5f671c09e..04d52361db0 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,4 +1,4 @@ -%tr{ class: "tree-item" } +%tr.tree-item %td.tree-item-file-name %i.fa.fa-archive.fa-fw = submodule_link(submodule_item, @ref) diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index d37c376c36b..038a960bd0c 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -1,13 +1,13 @@ -%div.tree-content-holder +.tree-content-holder .table-holder - %table.table#tree-slider{class: "table_#{@hex_path} tree-table" } + %table.table#tree-slider{ class: "table_#{@hex_path} tree-table" } %thead %tr %th Name %th.hidden-xs .pull-left Last commit .last-commit.hidden-sm.pull-left - %small.light + %small.light = clipboard_button(clipboard_text: @commit.id) = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" = time_ago_with_tooltip(@commit.committed_date) diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 1c5f8b3928b..259207a6dfd 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -15,11 +15,11 @@ - if current_user %li - if !on_top_of_branch? - %span.btn.add-to-tree.disabled.has-tooltip{title: "You can only add files when you are on a branch", data: { container: 'body' }} + %span.btn.add-to-tree.disabled.has-tooltip{ title: "You can only add files when you are on a branch", data: { container: 'body' } } = icon('plus') - else %span.dropdown - %a.dropdown-toggle.btn.add-to-tree{href: '#', "data-toggle" => "dropdown"} + %a.dropdown-toggle.btn.add-to-tree{ href: '#', "data-toggle" => "dropdown" } = icon('plus') %ul.dropdown-menu - if can_edit_tree? @@ -28,11 +28,11 @@ = icon('pencil fw') New file %li - = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal'} do + = link_to '#modal-upload-blob', { 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } do = icon('file fw') Upload file %li - = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal'} do + = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do = icon('folder fw') New directory - elsif can?(current_user, :fork_project, @project) diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index 1ccef6d52ab..15c9536133c 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -6,4 +6,4 @@ %span.str-truncated= path %td.hidden-xs.tree-commit %td.tree-time-ago.text-right - = render 'projects/tree/spinner'
\ No newline at end of file + = render 'projects/tree/spinner' diff --git a/app/views/projects/variables/index.html.haml b/app/views/projects/variables/index.html.haml index 39303700131..cf7ae0b489f 100644 --- a/app/views/projects/variables/index.html.haml +++ b/app/views/projects/variables/index.html.haml @@ -15,4 +15,4 @@ No variables found, add one with the form above. - else = render "table" - %button.btn.btn-info.js-btn-toggle-reveal-values{"data-status" => 'hidden'} Reveal Values + %button.btn.btn-info.js-btn-toggle-reveal-values{ "data-status" => 'hidden' } Reveal Values diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index c32cb122c26..c74f53b4c39 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -1,11 +1,11 @@ - @no_container = true %div{ class: container_class } - %div#modal-new-wiki.modal + #modal-new-wiki.modal .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title New Wiki Page .modal-body %form.new-wiki-page diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 2c378231237..8cbecb725b5 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -1,70 +1,70 @@ %ul.nav-links.search-filter - if @project - %li{class: ("active" if @scope == 'blobs')} + %li{ class: ("active" if @scope == 'blobs') } = link_to search_filter_path(scope: 'blobs') do Code %span.badge = @search_results.blobs_count - %li{class: ("active" if @scope == 'issues')} + %li{ class: ("active" if @scope == 'issues') } = link_to search_filter_path(scope: 'issues') do Issues %span.badge = @search_results.issues_count - %li{class: ("active" if @scope == 'merge_requests')} + %li{ class: ("active" if @scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests %span.badge = @search_results.merge_requests_count - %li{class: ("active" if @scope == 'milestones')} + %li{ class: ("active" if @scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones %span.badge = @search_results.milestones_count - %li{class: ("active" if @scope == 'notes')} + %li{ class: ("active" if @scope == 'notes') } = link_to search_filter_path(scope: 'notes') do Comments %span.badge = @search_results.notes_count - %li{class: ("active" if @scope == 'wiki_blobs')} + %li{ class: ("active" if @scope == 'wiki_blobs') } = link_to search_filter_path(scope: 'wiki_blobs') do Wiki %span.badge = @search_results.wiki_blobs_count - %li{class: ("active" if @scope == 'commits')} + %li{ class: ("active" if @scope == 'commits') } = link_to search_filter_path(scope: 'commits') do Commits %span.badge = @search_results.commits_count - elsif @show_snippets - %li{class: ("active" if @scope == 'snippet_blobs')} + %li{ class: ("active" if @scope == 'snippet_blobs') } = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do Snippet Contents %span.badge = @search_results.snippet_blobs_count - %li{class: ("active" if @scope == 'snippet_titles')} + %li{ class: ("active" if @scope == 'snippet_titles') } = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do Titles and Filenames %span.badge = @search_results.snippet_titles_count - else - %li{class: ("active" if @scope == 'projects')} + %li{ class: ("active" if @scope == 'projects') } = link_to search_filter_path(scope: 'projects') do Projects %span.badge = @search_results.projects_count - %li{class: ("active" if @scope == 'issues')} + %li{ class: ("active" if @scope == 'issues') } = link_to search_filter_path(scope: 'issues') do Issues %span.badge = @search_results.issues_count - %li{class: ("active" if @scope == 'merge_requests')} + %li{ class: ("active" if @scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests %span.badge = @search_results.merge_requests_count - %li{class: ("active" if @scope == 'milestones')} + %li{ class: ("active" if @scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones %span.badge diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 3b82d8e686f..96b75440309 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -2,12 +2,12 @@ .git-clone-holder.input-group .input-group-btn - -if allowed_protocols_present? + - if allowed_protocols_present? .clone-dropdown-btn.btn.btn-static %span = enabled_project_button(project, enabled_protocol) - else - %a#clone-dropdown.clone-dropdown-btn.btn{href: '#', data: { toggle: 'dropdown' }} + %a#clone-dropdown.clone-dropdown-btn.btn{ href: '#', data: { toggle: 'dropdown' } } %span = default_clone_protocol.upcase = icon('caret-down') diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index b0fc60573f7..e7cb93b17a7 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -1,8 +1,8 @@ -#modal-confirm-danger.modal{tabindex: -1} +#modal-confirm-danger.modal{ tabindex: -1 } .modal-dialog .modal-content .modal-header - %a.close{href: "#", "data-dismiss" => "modal"} × + %a.close{ href: "#", "data-dismiss" => "modal" } × %h3.page-title Confirmation required diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index e26693bf5b9..8d64cb5d698 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -9,8 +9,8 @@ - offset = defined?(first_line_number) ? first_line_number : 1 - i = index + offset -# We're not using `link_to` because it is too slow once we get to thousands of lines. - %a.diff-line-num{href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i} + %a.diff-line-num{ href: "#{link}#L#{i}", id: "L#{i}", 'data-line-number' => i } = link_icon = i - .blob-content{data: {blob_id: blob.id}} + .blob-content{ data: { blob_id: blob.id } } = highlight(blob.path, blob.data, repository: repository, plain: blob.no_highlighting?) diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index db324d8868e..f11f4471a9d 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -4,7 +4,7 @@ - status = label_subscription_status(label, @project).inquiry if current_user - subject = local_assigns[:subject] -%li{id: label_css_id, data: { id: label.id } } +%li{ id: label_css_id, data: { id: label.id } } = render "shared/label_row", label: label .visible-xs.visible-sm-inline-block.visible-md-inline-block.dropdown @@ -56,7 +56,7 @@ = icon('spinner spin', class: 'label-subscribe-button-loading') .dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) } - %button.dropdown-menu-toggle{type: 'button', 'data-toggle' => 'dropdown'} + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } %span Subscribe = icon('chevron-down') %ul.dropdown-menu diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index 186ed4a7c8b..39294fe1a09 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -2,17 +2,17 @@ - counts = milestone_counts(@project.milestones) %ul.nav-links - %li{class: milestone_class_for_state(params[:state], 'opened', true)}> + %li{ class: milestone_class_for_state(params[:state], 'opened', true) }> = link_to milestones_filter_path(state: 'opened') do Open - if @project %span.badge #{counts[:opened]} - %li{class: milestone_class_for_state(params[:state], 'closed')}> + %li{ class: milestone_class_for_state(params[:state], 'closed') }> = link_to milestones_filter_path(state: 'closed') do Closed - if @project %span.badge #{counts[:closed]} - %li{class: milestone_class_for_state(params[:state], 'all')}> + %li{ class: milestone_class_for_state(params[:state], 'all') }> = link_to milestones_filter_path(state: 'all') do All - if @project diff --git a/app/views/shared/_nav_scroll.html.haml b/app/views/shared/_nav_scroll.html.haml index 4e3b1b3a571..61646f150c1 100644 --- a/app/views/shared/_nav_scroll.html.haml +++ b/app/views/shared/_nav_scroll.html.haml @@ -1,4 +1,4 @@ .fade-left = icon('angle-left') .fade-right - = icon('angle-right')
\ No newline at end of file + = icon('angle-right') diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index ede3c7090d7..0ce0d759e86 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,5 +1,5 @@ .dropdown.inline.prepend-left-10 - %button.dropdown-toggle{type: 'button', data: {toggle: 'dropdown'}} + %button.dropdown-toggle{ type: 'button', data: {toggle: 'dropdown' } } %span.light - if @sort.present? = sort_options_hash[@sort] diff --git a/app/views/shared/groups/_group.html.haml b/app/views/shared/groups/_group.html.haml index 8164f61797c..f9a7aa4e29b 100644 --- a/app/views/shared/groups/_group.html.haml +++ b/app/views/shared/groups/_group.html.haml @@ -21,7 +21,7 @@ = icon('users') = number_with_delimiter(group.users.count) - %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group)} + %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(group) } = visibility_level_icon(group.visibility_level, fw: false) .avatar-container.s40 diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index e3503981afe..b42eaabb111 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -31,11 +31,11 @@ - if issuable_filter_present? .filter-item.inline.reset-filters - %a{href: page_filter_path(without: issuable_filter_params)} Reset filters + %a{ href: page_filter_path(without: issuable_filter_params) } Reset filters .pull-right - if boards_page - #js-boards-seach.issue-boards-search + #js-boards-search.issue-boards-search %input.pull-left.form-control{ type: "search", placeholder: "Filter by name...", "v-model" => "filters.search", "debounce" => "250" } - if can?(current_user, :admin_list, @project) .dropdown.pull-right @@ -56,9 +56,9 @@ = dropdown_tag("Status", options: { toggle_class: "js-issue-status", title: "Change status", dropdown_class: "dropdown-menu-status dropdown-menu-selectable", data: { field_name: "update[state_event]" } } ) do %ul %li - %a{href: "#", data: {id: "reopen"}} Open + %a{ href: "#", data: { id: "reopen" } } Open %li - %a{href: "#", data: {id: "close"}} Closed + %a{ href: "#", data: {id: "close" } } Closed .filter-item.inline = dropdown_tag("Assignee", options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: "Assign to", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable", placeholder: "Search authors", data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: "update[assignee_id]" } }) @@ -70,9 +70,9 @@ = dropdown_tag("Subscription", options: { toggle_class: "js-subscription-event", title: "Change subscription", dropdown_class: "dropdown-menu-selectable", data: { field_name: "update[subscription_event]" } } ) do %ul %li - %a{href: "#", data: {id: "subscribe"}} Subscribe + %a{ href: "#", data: { id: "subscribe" } } Subscribe %li - %a{href: "#", data: {id: "unsubscribe"}} Unsubscribe + %a{ href: "#", data: { id: "unsubscribe" } } Unsubscribe = hidden_field_tag 'update[issuable_ids]', [] = hidden_field_tag :state_event, params[:state_event] diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index bdb00bfa33c..dcc1f3ba676 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -58,7 +58,7 @@ as resolved. Ask someone with sufficient rights to resolve the them. - is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) -.row-content-block{class: (is_footer ? "footer-block" : "middle-block")} +.row-content-block{ class: (is_footer ? "footer-block" : "middle-block") } - if issuable.new_record? = form.submit "Submit #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' - else diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 22b5a6aa11b..93c7fa0c7d6 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -19,7 +19,7 @@ = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : label.try(:title), id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data} + %button.dropdown-menu-toggle.js-label-select.js-multiselect{ class: classes.join(' '), type: "button", data: dropdown_data } %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) } = multi_label_name(selected, "Labels") = icon('chevron-down') diff --git a/app/views/shared/issuable/_label_page_default.html.haml b/app/views/shared/issuable/_label_page_default.html.haml index a8f01026ca5..9a8529c6cbb 100644 --- a/app/views/shared/issuable/_label_page_default.html.haml +++ b/app/views/shared/issuable/_label_page_default.html.haml @@ -17,7 +17,7 @@ %ul.dropdown-footer-list - if can?(current_user, :admin_label, @project) %li - %a.dropdown-toggle-page{href: "#"} + %a.dropdown-toggle-page{ href: "#" } Create new label %li = link_to namespace_project_labels_path(@project.namespace, @project), :"data-is-link" => true do diff --git a/app/views/shared/issuable/_nav.html.haml b/app/views/shared/issuable/_nav.html.haml index d938edf4dbd..1154316c03f 100644 --- a/app/views/shared/issuable/_nav.html.haml +++ b/app/views/shared/issuable/_nav.html.haml @@ -3,23 +3,23 @@ - issuables = @issues || @merge_requests %ul.nav-links.issues-state-filters - %li{class: ("active" if params[:state] == 'opened')}> + %li{ class: ("active" if params[:state] == 'opened') }> = link_to page_filter_path(state: 'opened', label: true), id: 'state-opened', title: "Filter by #{page_context_word} that are currently opened." do #{issuables_state_counter_text(type, :opened)} - if type == :merge_requests - %li{class: ("active" if params[:state] == 'merged')}> + %li{ class: ("active" if params[:state] == 'merged') }> = link_to page_filter_path(state: 'merged', label: true), id: 'state-merged', title: 'Filter by merge requests that are currently merged.' do #{issuables_state_counter_text(type, :merged)} - %li{class: ("active" if params[:state] == 'closed')}> + %li{ class: ("active" if params[:state] == 'closed') }> = link_to page_filter_path(state: 'closed', label: true), id: 'state-closed', title: 'Filter by merge requests that are currently closed and unmerged.' do #{issuables_state_counter_text(type, :closed)} - else - %li{class: ("active" if params[:state] == 'closed')}> + %li{ class: ("active" if params[:state] == 'closed') }> = link_to page_filter_path(state: 'closed', label: true), id: 'state-all', title: 'Filter by issues that are currently closed.' do #{issuables_state_counter_text(type, :closed)} - %li{class: ("active" if params[:state] == 'all')}> + %li{ class: ("active" if params[:state] == 'all') }> = link_to page_filter_path(state: 'all', label: true), id: 'state-all', title: "Show all #{page_context_word}." do #{issuables_state_counter_text(type, :all)} diff --git a/app/views/shared/issuable/_participants.html.haml b/app/views/shared/issuable/_participants.html.haml index 33a9a494857..171da899937 100644 --- a/app/views/shared/issuable/_participants.html.haml +++ b/app/views/shared/issuable/_participants.html.haml @@ -13,8 +13,8 @@ .participants-author.js-participants-author = link_to_member(@project, participant, name: false, size: 24) - if participants_extra > 0 - %div.participants-more - %a.js-participants-more{href: "#", data: {original_text: "+ #{participants_size - 7} more", less_text: "- show less"}} + .participants-more + %a.js-participants-more{ href: "#", data: { original_text: "+ #{participants_size - 7} more", less_text: "- show less" } } + #{participants_extra} more :javascript IssuableContext.prototype.PARTICIPANTS_ROW_COUNT = #{participants_row}; diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 9fe1be5a597..bc57d48ae7c 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -17,9 +17,9 @@ Add todo = icon('spin spinner', class: 'hidden js-issuable-todo-loading') - = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| + = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, format: :json, html: { class: 'issuable-context-form inline-update js-issuable-update' } do |f| .block.assignee - .sidebar-collapsed-icon.sidebar-collapsed-user{data: {toggle: "tooltip", placement: "left", container: "body"}, title: (issuable.assignee.name if issuable.assignee)} + .sidebar-collapsed-icon.sidebar-collapsed-user{ data: { toggle: "tooltip", placement: "left", container: "body" }, title: (issuable.assignee.name if issuable.assignee) } - if issuable.assignee = link_to_member(@project, issuable.assignee, size: 24) - else @@ -54,7 +54,7 @@ = icon('clock-o') %span - if issuable.milestone - %span.has-tooltip{title: milestone_remaining_days(issuable.milestone), data: {container: 'body', html: 1, placement: 'left'}} + %span.has-tooltip{ title: milestone_remaining_days(issuable.milestone), data: { container: 'body', html: 1, placement: 'left' } } = issuable.milestone.title - else None @@ -129,8 +129,8 @@ - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}} - %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?)} + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project) } } + %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } = multi_label_name(selected_labels, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable @@ -141,7 +141,7 @@ = render "shared/issuable/participants", participants: issuable.participants(current_user) - if current_user - subscribed = issuable.subscribed?(current_user, @project) - .block.light.subscription{data: {url: toggle_subscription_path(issuable)}} + .block.light.subscription{ data: { url: toggle_subscription_path(issuable) } } .sidebar-collapsed-icon = icon('rss') %span.issuable-header-text.hide-collapsed.pull-left @@ -157,7 +157,7 @@ .cross-project-reference.hide-collapsed %span Reference: - %cite{title: project_ref} + %cite{ title: project_ref } = project_ref = clipboard_button(clipboard_text: project_ref) diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml index 8928de9097b..a46ba3b0605 100644 --- a/app/views/shared/members/_group.html.haml +++ b/app/views/shared/members/_group.html.haml @@ -3,7 +3,7 @@ - can_admin_member = can?(current_user, :admin_project_member, @project) - dom_id = "group_member_#{group_link.id}" %li.member.group_member{ id: dom_id } - %span{ class: "list-item-name" } + %span.list-item-name = image_tag group_icon(group), class: "avatar s40", alt: '' %strong = link_to group.name, group_path(group) diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 9e1b0379428..51c195ffbcd 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -14,15 +14,15 @@ - if issuable.is_a?(Issue) = confidential_icon(issuable) = link_to_gfm issuable.title, [project.namespace.becomes(Namespace), project, issuable], title: issuable.title - %div{class: 'issuable-detail'} + .issuable-detail = link_to [project.namespace.becomes(Namespace), project, issuable] do - %span{ class: 'issuable-number' }>= issuable.to_reference + %span.issuable-number >= issuable.to_reference - issuable.labels.each do |label| = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, label_name: label.title, state: 'all' }) do - render_colored_label(label) - %span{ class: "assignee-icon" } + %span.assignee-icon - if assignee = link_to polymorphic_path(base_url_args, { milestone_title: @milestone.title, assignee_id: issuable.assignee_id, state: 'all' }), class: 'has-tooltip', title: "Assigned to #{assignee.name}", data: { container: 'body' } do diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index 3dccfb147bf..3200aacf542 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -1,7 +1,7 @@ - dashboard = local_assigns[:dashboard] - custom_dom_id = dom_id(@project ? milestone : milestone.milestones.first) -%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } +%li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } .row .col-sm-6 %strong= link_to_gfm truncate(milestone.title, length: 100), milestone_path diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index a82fc95df84..b5c0a7fd6d4 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -18,7 +18,7 @@ %p Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out = succeed "." do - %a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank"} notification emails + %a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank" } notification emails .col-lg-8 - NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index| - field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]" diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml index 264391fe84f..4a27965754d 100644 --- a/app/views/shared/projects/_project.html.haml +++ b/app/views/shared/projects/_project.html.haml @@ -25,7 +25,7 @@ %span = icon('star') = number_with_delimiter(project.star_count) - %span.visibility-icon.has-tooltip{data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project)} + %span.visibility-icon.has-tooltip{ data: { container: 'body', placement: 'left' }, title: visibility_icon_description(project) } = visibility_level_icon(project.visibility_level, fw: true) .title diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml index dcdba01aee9..ad5c0c2d8c8 100644 --- a/app/views/shared/snippets/_blob.html.haml +++ b/app/views/shared/snippets/_blob.html.haml @@ -1,6 +1,6 @@ - unless @snippet.content.empty? - if markup?(@snippet.file_name) - %textarea.markdown-snippet-copy.blob-content{data: {blob_id: @snippet.id}} + %textarea.markdown-snippet-copy.blob-content{ data: { blob_id: @snippet.id } } = @snippet.content .file-content.wiki - if gitlab_markdown?(@snippet.file_name) diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml index 94d4dd4fa7d..92151176fce 100644 --- a/app/views/sherlock/file_samples/show.html.haml +++ b/app/views/sherlock/file_samples/show.html.haml @@ -41,7 +41,7 @@ %th= t('sherlock.percent') %tbody - @file_sample.line_samples.each_with_index do |sample, index| - %tr{class: sample.majority_of?(@file_sample.duration) ? 'slow' : ''} + %tr{ class: sample.majority_of?(@file_sample.duration) ? 'slow' : '' } %td= index + 1 %td= sample.events %td diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml index 7073c0f4d90..5a447f791dc 100644 --- a/app/views/sherlock/queries/_general.html.haml +++ b/app/views/sherlock/queries/_general.html.haml @@ -26,7 +26,7 @@ .panel.panel-default .panel-heading .pull-right - %button.js-clipboard-trigger.btn.btn-xs{title: t('sherlock.copy_to_clipboard'), type: :button} + %button.js-clipboard-trigger.btn.btn-xs{ title: t('sherlock.copy_to_clipboard'), type: :button } %i.fa.fa-clipboard %pre.hidden = @query.formatted_query @@ -41,7 +41,7 @@ .panel.panel-default .panel-heading .pull-right - %button.js-clipboard-trigger.btn.btn-xs{title: t('sherlock.copy_to_clipboard'), type: :button} + %button.js-clipboard-trigger.btn.btn-xs{ title: t('sherlock.copy_to_clipboard'), type: :button } %i.fa.fa-clipboard %pre.hidden = @query.explain diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml index fc2863dca8e..c45da6ee9a4 100644 --- a/app/views/sherlock/queries/show.html.haml +++ b/app/views/sherlock/queries/show.html.haml @@ -3,10 +3,10 @@ %ul.nav-links %li.active - %a(href="#tab-general" data-toggle="tab") + %a{ href: "#tab-general", data: { toggle: "tab" } } = t('sherlock.general') %li - %a(href="#tab-backtrace" data-toggle="tab") + %a{ href: "#tab-backtrace", data: { toggle: "tab" } } = t('sherlock.backtrace') .row-content-block diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml index da969c02765..bc05659dfa8 100644 --- a/app/views/sherlock/transactions/index.html.haml +++ b/app/views/sherlock/transactions/index.html.haml @@ -28,7 +28,7 @@ %tr %td= trans.type %td - %span{title: trans.path} + %span{ title: trans.path } = truncate(trans.path, length: 70) %td = trans.duration.round(2) diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml index 8aa6b437d95..eab91e8fbe4 100644 --- a/app/views/sherlock/transactions/show.html.haml +++ b/app/views/sherlock/transactions/show.html.haml @@ -3,15 +3,15 @@ %ul.nav-links %li.active - %a(href="#tab-general" data-toggle="tab") + %a{ href: "#tab-general", data: { toggle: "tab" } } = t('sherlock.general') %li - %a(href="#tab-queries" data-toggle="tab") + %a{ href: "#tab-queries", data: { toggle: "tab" } } = t('sherlock.queries') %span.badge #{@transaction.queries.length} %li - %a(href="#tab-file-samples" data-toggle="tab") + %a{ href: "#tab-file-samples", data: { toggle: "tab" } } = t('sherlock.file_samples') %span.badge #{@transaction.file_samples.length} diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index fa998c91f72..f878bece2fa 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -1,30 +1,21 @@ #js-authenticate-u2f +%a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code %script#js-authenticate-u2f-not-supported{ type: "text/template" } %p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer). -%script#js-authenticate-u2f-setup{ type: "text/template" } - %div - %p Insert your security key (if you haven't already), and press the button below. - %a.btn.btn-info#js-login-u2f-device{ href: 'javascript:void(0)' } Sign in via U2F device - %script#js-authenticate-u2f-in-progress{ type: "text/template" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. %script#js-authenticate-u2f-error{ type: "text/template" } %div %p <%= error_message %> (error code: <%= error_code %>) - %a.btn.btn-warning#js-u2f-try-again Try again? + %a.btn.btn-block.btn-warning#js-u2f-try-again Try again? %script#js-authenticate-u2f-authenticated{ type: "text/template" } %div - %p We heard back from your U2F device. Click this button to authenticate with the GitLab server. - = form_tag(new_user_session_path, method: :post) do |f| + %p We heard back from your U2F device. You have been authenticated. + = form_tag(new_user_session_path, method: :post, id: 'js-login-u2f-form') do |f| - resource_params = params[resource_name].presence || params = hidden_field_tag 'user[remember_me]', resource_params.fetch(:remember_me, 0) = hidden_field_tag 'user[device_response]', nil, class: 'form-control', required: true, id: "js-device-response" - = submit_tag "Authenticate via U2F Device", class: "btn btn-success" - -:javascript - var u2fAuthenticate = new U2FAuthenticate($("#js-authenticate-u2f"), gon.u2f); - u2fAuthenticate.start(); diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml index fcc33f04237..adc07bcba73 100644 --- a/app/views/u2f/_register.html.haml +++ b/app/views/u2f/_register.html.haml @@ -27,7 +27,7 @@ %a.btn.btn-warning#js-u2f-try-again Try again? %script#js-register-u2f-registered{ type: "text/template" } - %div.row.append-bottom-10 + .row.append-bottom-10 .col-md-12 %p Your device was successfully set up! Give it a name and register it with the GitLab server. = form_tag(create_u2f_profile_two_factor_auth_path, method: :post) do diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 09ff8a76d27..6228245d8d0 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -6,4 +6,4 @@ new Calendar( #{@activity_dates.to_json}, '#{user_calendar_activities_path}' - );
\ No newline at end of file + ); diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 1e0752bd3c3..fb25eed4f37 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -18,11 +18,11 @@ - elsif current_user - if @user.abuse_report %button.btn.btn-danger{ title: 'Already reported for abuse', - data: { toggle: 'tooltip', placement: 'bottom', container: 'body' }} + data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } } = icon('exclamation-circle') - else = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn btn-gray', - title: 'Report abuse', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do + title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - if current_user = link_to user_path(@user, :atom, { private_token: current_user.private_token }), class: 'btn btn-gray' do @@ -101,12 +101,12 @@ .tab-content #activity.tab-pane .row-content-block.calender-block.white.second-block.hidden-xs - .user-calendar{data: {href: user_calendar_path}} + .user-calendar{ data: { href: user_calendar_path } } %h4.center.light %i.fa.fa-spinner.fa-spin .user-calendar-activities - .content_list{ data: {href: user_path} } + .content_list{ data: { href: user_path } } = spinner #groups.tab-pane diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb index 27d7e652721..8ff9d07860f 100644 --- a/app/workers/project_cache_worker.rb +++ b/app/workers/project_cache_worker.rb @@ -6,26 +6,27 @@ class ProjectCacheWorker LEASE_TIMEOUT = 15.minutes.to_i # project_id - The ID of the project for which to flush the cache. - # refresh - An Array containing extra types of data to refresh such as - # `:readme` to flush the README and `:changelog` to flush the - # CHANGELOG. - def perform(project_id, refresh = []) + # files - An Array containing extra types of files to refresh such as + # `:readme` to flush the README and `:changelog` to flush the + # CHANGELOG. + # statistics - An Array containing columns from ProjectStatistics to + # refresh, if empty all columns will be refreshed + def perform(project_id, files = [], statistics = []) project = Project.find_by(id: project_id) return unless project && project.repository.exists? - update_repository_size(project) - project.update_commit_count + update_statistics(project, statistics.map(&:to_sym)) - project.repository.refresh_method_caches(refresh.map(&:to_sym)) + project.repository.refresh_method_caches(files.map(&:to_sym)) end - def update_repository_size(project) - return unless try_obtain_lease_for(project.id, :update_repository_size) + def update_statistics(project, statistics = []) + return unless try_obtain_lease_for(project.id, :update_statistics) - Rails.logger.info("Updating repository size for project #{project.id}") + Rails.logger.info("Updating statistics for project #{project.id}") - project.update_repository_size + project.statistics.refresh!(only: statistics) end private diff --git a/changelogs/unreleased/18786-go-to-a-project-order.yml b/changelogs/unreleased/18786-go-to-a-project-order.yml new file mode 100644 index 00000000000..1b9e246d1a7 --- /dev/null +++ b/changelogs/unreleased/18786-go-to-a-project-order.yml @@ -0,0 +1,4 @@ +--- +title: Go to a project order +merge_request: 7737 +author: Jacopo Beschi @jacopo-beschi diff --git a/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml b/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml new file mode 100644 index 00000000000..5570ede4a9a --- /dev/null +++ b/changelogs/unreleased/19988-prevent-empty-pagination-when-list-not-empty.yml @@ -0,0 +1,4 @@ +--- +title: Prevent empty pagination when list is not empty +merge_request: 8172 +author: diff --git a/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml b/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml new file mode 100644 index 00000000000..574c322803c --- /dev/null +++ b/changelogs/unreleased/21135-resolve-these-conflicts-link-is-too-subtle.yml @@ -0,0 +1,4 @@ +--- +title: Improve visibility of "Resolve conflicts" and "Merge locally" actions +merge_request: 8229 +author: diff --git a/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml b/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml new file mode 100644 index 00000000000..fac7697ede1 --- /dev/null +++ b/changelogs/unreleased/24820-buttons-in-the-branches-page-are-stacking-on-top-of-each-other-depending-on-the-selected-filter.yml @@ -0,0 +1,4 @@ +--- +title: fix button layout issue on branches page +merge_request: 8074 +author: diff --git a/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml b/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml new file mode 100644 index 00000000000..c31c89dc4bc --- /dev/null +++ b/changelogs/unreleased/24876-page-jumps-to-wrong-position-when-clicking-a-comment-anchor.yml @@ -0,0 +1,4 @@ +--- +title: ensure permalinks scroll to correct position on multiple clicks +merge_request: 8046 +author: diff --git a/changelogs/unreleased/24941-login-tabs-border.yml b/changelogs/unreleased/24941-login-tabs-border.yml new file mode 100644 index 00000000000..b06c21ad71a --- /dev/null +++ b/changelogs/unreleased/24941-login-tabs-border.yml @@ -0,0 +1,4 @@ +--- +title: fix border in login session tabs +merge_request: 8346 +author: diff --git a/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml b/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml new file mode 100644 index 00000000000..e60c42cdfba --- /dev/null +++ b/changelogs/unreleased/25430-make-colors-in-runner-status-more-colorblind-friendly.yml @@ -0,0 +1,4 @@ +--- +title: Change status colors of runners to better defaults +merge_request: +author: diff --git a/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml b/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml new file mode 100644 index 00000000000..850e98518a6 --- /dev/null +++ b/changelogs/unreleased/25705-your-commands-have-been-executed-is-overkill.yml @@ -0,0 +1,3 @@ +--- +title: Replace wording for slash command confirmation message +merge_request: 8123 diff --git a/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml b/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml new file mode 100644 index 00000000000..c82bacd8bcd --- /dev/null +++ b/changelogs/unreleased/25829-update-username-button-remains-disabled-upon-failure.yml @@ -0,0 +1,4 @@ +--- +title: re-enable change username button after failure +merge_request: 8332 +author: diff --git a/changelogs/unreleased/25930-discussion-actions-overlap-header-text.yml b/changelogs/unreleased/25930-discussion-actions-overlap-header-text.yml deleted file mode 100644 index 54a461c24ed..00000000000 --- a/changelogs/unreleased/25930-discussion-actions-overlap-header-text.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fix discussion overlap text in regular screens -merge_request: 8273 -author: diff --git a/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml b/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml new file mode 100644 index 00000000000..e05e2dd6fed --- /dev/null +++ b/changelogs/unreleased/25996-Move-award-emoji-out-of-the-discussion-tab-for-MR.yml @@ -0,0 +1,4 @@ +--- +title: Move award emoji's out of the discussion tab for merge requests +merge_request: +author: diff --git a/changelogs/unreleased/26018-mini-pipeline-hover-cross-broswer.yml b/changelogs/unreleased/26018-mini-pipeline-hover-cross-broswer.yml deleted file mode 100644 index 501f0b25a21..00000000000 --- a/changelogs/unreleased/26018-mini-pipeline-hover-cross-broswer.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixes mini-pipeline-graph dropdown animation and stage position in chrome, firefox and safari -merge_request: 8282 -author: diff --git a/changelogs/unreleased/26126-cache-even-when-no-projects.yml b/changelogs/unreleased/26126-cache-even-when-no-projects.yml new file mode 100644 index 00000000000..53e14ac9edf --- /dev/null +++ b/changelogs/unreleased/26126-cache-even-when-no-projects.yml @@ -0,0 +1,4 @@ +--- +title: Cache project authorizations even when user has access to zero projects +merge_request: 8327 +author: diff --git a/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml b/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml new file mode 100644 index 00000000000..40183f8d3fa --- /dev/null +++ b/changelogs/unreleased/26134-ctrl-enter-does-not-submit-a-new-merge-request.yml @@ -0,0 +1,4 @@ +--- +title: Make CTRL+Enter submits a new merge request +merge_request: 8360 +author: Saad Shahd diff --git a/changelogs/unreleased/26192-fixes-too-short-input.yml b/changelogs/unreleased/26192-fixes-too-short-input.yml new file mode 100644 index 00000000000..ff707f4694d --- /dev/null +++ b/changelogs/unreleased/26192-fixes-too-short-input.yml @@ -0,0 +1,4 @@ +--- +title: Fixes too short input for placeholder message in commit listing page +merge_request: 8367 +author: diff --git a/changelogs/unreleased/26218-rety-button-pipeline-builds-name-drodown-broken.yml b/changelogs/unreleased/26218-rety-button-pipeline-builds-name-drodown-broken.yml new file mode 100644 index 00000000000..ef8581b6fb3 --- /dev/null +++ b/changelogs/unreleased/26218-rety-button-pipeline-builds-name-drodown-broken.yml @@ -0,0 +1,4 @@ +--- +title: Increases width of mini-pipeline-graph dropdown to prevent wrong position on chrome on ubuntu +merge_request: 8399 +author: diff --git a/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml b/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml new file mode 100644 index 00000000000..74412c32375 --- /dev/null +++ b/changelogs/unreleased/7898-fixes-issue-boards-list-colored-top-border-visual-glitch.yml @@ -0,0 +1,4 @@ +--- +title: Fixes issue boards list colored top border visual glitch +merge_request: 7898 +author: Pier Paolo Ramon diff --git a/changelogs/unreleased/dz-improve-admin-group-routing.yml b/changelogs/unreleased/dz-improve-admin-group-routing.yml new file mode 100644 index 00000000000..2360b965c90 --- /dev/null +++ b/changelogs/unreleased/dz-improve-admin-group-routing.yml @@ -0,0 +1,4 @@ +--- +title: Fix 500 error when visit group from admin area if group name contains dot +merge_request: +author: diff --git a/changelogs/unreleased/dz-nested-group-misc.yml b/changelogs/unreleased/dz-nested-group-misc.yml new file mode 100644 index 00000000000..9c9d0b1c644 --- /dev/null +++ b/changelogs/unreleased/dz-nested-group-misc.yml @@ -0,0 +1,4 @@ +--- +title: Show nested groups tab on group page +merge_request: 8308 +author: diff --git a/changelogs/unreleased/dz-rename-invalid-users.yml b/changelogs/unreleased/dz-rename-invalid-users.yml new file mode 100644 index 00000000000..f420b069531 --- /dev/null +++ b/changelogs/unreleased/dz-rename-invalid-users.yml @@ -0,0 +1,4 @@ +--- +title: Rename users with namespace ending with .git +merge_request: 8309 +author: diff --git a/changelogs/unreleased/dz-rename-reserved-project-names.yml b/changelogs/unreleased/dz-rename-reserved-project-names.yml new file mode 100644 index 00000000000..30bcc1a0396 --- /dev/null +++ b/changelogs/unreleased/dz-rename-reserved-project-names.yml @@ -0,0 +1,4 @@ +--- +title: Rename projects wth reserved names +merge_request: 8234 +author: diff --git a/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml b/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml new file mode 100644 index 00000000000..0fd590a877b --- /dev/null +++ b/changelogs/unreleased/feature-1376-allow-write-access-deploy-keys.yml @@ -0,0 +1,4 @@ +--- +title: Allow to add deploy keys with write-access +merge_request: 5807 +author: Ali Ibrahim diff --git a/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml b/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml new file mode 100644 index 00000000000..0c0b74b686a --- /dev/null +++ b/changelogs/unreleased/feature-admin-merge-groups-and-projects.yml @@ -0,0 +1,4 @@ +--- +title: Merged the 'Groups' and 'Projects' tabs when viewing user profiles +merge_request: 8323 +author: James Gregory diff --git a/changelogs/unreleased/feature-more-storage-statistics.yml b/changelogs/unreleased/feature-more-storage-statistics.yml new file mode 100644 index 00000000000..824fd36dc34 --- /dev/null +++ b/changelogs/unreleased/feature-more-storage-statistics.yml @@ -0,0 +1,4 @@ +--- +title: Add more storage statistics +merge_request: 7754 +author: Markus Koller diff --git a/changelogs/unreleased/filename-to-file-path.yml b/changelogs/unreleased/filename-to-file-path.yml new file mode 100644 index 00000000000..3c6c838595a --- /dev/null +++ b/changelogs/unreleased/filename-to-file-path.yml @@ -0,0 +1,3 @@ +--- +title: Rename filename to file path in tooltip of file header in merge request diff +merge_request: 8314
\ No newline at end of file diff --git a/changelogs/unreleased/fix-api-deprecation.yml b/changelogs/unreleased/fix-api-deprecation.yml new file mode 100644 index 00000000000..90285ddf058 --- /dev/null +++ b/changelogs/unreleased/fix-api-deprecation.yml @@ -0,0 +1,4 @@ +--- +title: Fix a Grape deprecation, use `#request_method` instead of `#route_method` +merge_request: +author: diff --git a/changelogs/unreleased/fix-boards-search-typo.yml b/changelogs/unreleased/fix-boards-search-typo.yml new file mode 100644 index 00000000000..0c083fc0d10 --- /dev/null +++ b/changelogs/unreleased/fix-boards-search-typo.yml @@ -0,0 +1,4 @@ +--- +title: 'Fix typo: seach to search' +merge_request: 8370 +author: diff --git a/changelogs/unreleased/fix-light-hr-in-descriptions.yml b/changelogs/unreleased/fix-light-hr-in-descriptions.yml new file mode 100644 index 00000000000..8efd471e416 --- /dev/null +++ b/changelogs/unreleased/fix-light-hr-in-descriptions.yml @@ -0,0 +1,4 @@ +--- +title: Darkened hr border color in descriptions because of update of bootstrap +merge_request: 8333 +author: diff --git a/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml b/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml new file mode 100644 index 00000000000..1d001e6b568 --- /dev/null +++ b/changelogs/unreleased/fix-mentioned-issue-text-grammar.yml @@ -0,0 +1,4 @@ +--- +title: Fix a minor grammar error in merge request widget +merge_request: 8337 +author: diff --git a/changelogs/unreleased/gfm-new-line-fix.yml b/changelogs/unreleased/gfm-new-line-fix.yml new file mode 100644 index 00000000000..751bb356b5f --- /dev/null +++ b/changelogs/unreleased/gfm-new-line-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fixed GFM dropdown not showing on new lines +merge_request: +author: diff --git a/changelogs/unreleased/issue_22664.yml b/changelogs/unreleased/issue_22664.yml new file mode 100644 index 00000000000..18a8d9ec6be --- /dev/null +++ b/changelogs/unreleased/issue_22664.yml @@ -0,0 +1,4 @@ +--- +title: Check if user can read project before being assigned to issue +merge_request: +author: diff --git a/changelogs/unreleased/issue_25578.yml b/changelogs/unreleased/issue_25578.yml new file mode 100644 index 00000000000..e10f1d232af --- /dev/null +++ b/changelogs/unreleased/issue_25578.yml @@ -0,0 +1,4 @@ +--- +title: Fix redirect after update file when user has forked project +merge_request: +author: diff --git a/changelogs/unreleased/issue_25682.yml b/changelogs/unreleased/issue_25682.yml new file mode 100644 index 00000000000..a50138756ba --- /dev/null +++ b/changelogs/unreleased/issue_25682.yml @@ -0,0 +1,4 @@ +--- +title: Parse JIRA issue references even if Issue Tracker is disabled +merge_request: +author: diff --git a/changelogs/unreleased/issues-8081.yml b/changelogs/unreleased/issues-8081.yml new file mode 100644 index 00000000000..82f746937bc --- /dev/null +++ b/changelogs/unreleased/issues-8081.yml @@ -0,0 +1,4 @@ +--- +title: change 'gray' color theme name to 'black' to match the actual color +merge_request: 7908 +author: BM5k diff --git a/changelogs/unreleased/mentioned-but-not-closed-issues-messages.yml b/changelogs/unreleased/mentioned-but-not-closed-issues-messages.yml new file mode 100644 index 00000000000..ba3b13bcdb7 --- /dev/null +++ b/changelogs/unreleased/mentioned-but-not-closed-issues-messages.yml @@ -0,0 +1,4 @@ +--- +title: Fix unclear closing issue behaviour on Merge Request show page +merge_request: 8345 +author: Gabriel Gizotti diff --git a/changelogs/unreleased/re-style-issue-new-branch.yml b/changelogs/unreleased/re-style-issue-new-branch.yml new file mode 100644 index 00000000000..977a54ff2ae --- /dev/null +++ b/changelogs/unreleased/re-style-issue-new-branch.yml @@ -0,0 +1,3 @@ +--- +title: Remove checking branches state in issue new branch button +merge_request: 8023 diff --git a/changelogs/unreleased/view-ce-vs-ee.yml b/changelogs/unreleased/view-ce-vs-ee.yml new file mode 100644 index 00000000000..38bce4ac7c3 --- /dev/null +++ b/changelogs/unreleased/view-ce-vs-ee.yml @@ -0,0 +1,4 @@ +--- +title: About GitLab link in sidebar that links to help page +merge_request: 8316 +author: diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 3d1a41a4652..d4197da3fa9 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -10,5 +10,5 @@ # end # ActiveSupport::Inflector.inflections do |inflect| - inflect.uncountable %w(award_emoji) + inflect.uncountable %w(award_emoji project_statistics) end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 0dd2c8f7aef..8e99239f350 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -6,7 +6,6 @@ namespace :admin do member do get :projects get :keys - get :groups put :block put :unblock put :unlock @@ -32,7 +31,7 @@ namespace :admin do scope(path: 'groups/*id', controller: :groups, - constraints: { id: Gitlab::Regex.namespace_route_regex }) do + constraints: { id: Gitlab::Regex.namespace_route_regex, format: /(html|json|atom)/ }) do scope(as: :group) do put :members_update diff --git a/db/migrate/20160811172945_add_can_push_to_keys.rb b/db/migrate/20160811172945_add_can_push_to_keys.rb new file mode 100644 index 00000000000..5fd303fe8fb --- /dev/null +++ b/db/migrate/20160811172945_add_can_push_to_keys.rb @@ -0,0 +1,14 @@ +class AddCanPushToKeys < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default(:keys, :can_push, :boolean, default: false, allow_null: false) + end + + def down + remove_column(:keys, :can_push) + end +end diff --git a/db/migrate/20161201155511_create_project_statistics.rb b/db/migrate/20161201155511_create_project_statistics.rb new file mode 100644 index 00000000000..26e6d3623eb --- /dev/null +++ b/db/migrate/20161201155511_create_project_statistics.rb @@ -0,0 +1,20 @@ +class CreateProjectStatistics < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + # use bigint columns to support values >2GB + counter_column = { limit: 8, null: false, default: 0 } + + create_table :project_statistics do |t| + t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade } + t.references :namespace, null: false, index: true + t.integer :commit_count, counter_column + t.integer :storage_size, counter_column + t.integer :repository_size, counter_column + t.integer :lfs_objects_size, counter_column + t.integer :build_artifacts_size, counter_column + end + end +end diff --git a/db/migrate/20161201160452_migrate_project_statistics.rb b/db/migrate/20161201160452_migrate_project_statistics.rb new file mode 100644 index 00000000000..3ae3f2c159b --- /dev/null +++ b/db/migrate/20161201160452_migrate_project_statistics.rb @@ -0,0 +1,23 @@ +class MigrateProjectStatistics < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = true + DOWNTIME_REASON = 'Removes two columns from the projects table' + + def up + # convert repository_size in float (megabytes) to integer (bytes), + # initialize total storage_size with repository_size + execute <<-EOF + INSERT INTO project_statistics (project_id, namespace_id, commit_count, storage_size, repository_size) + SELECT id, namespace_id, commit_count, (repository_size * 1024 * 1024), (repository_size * 1024 * 1024) FROM projects + EOF + + remove_column :projects, :repository_size + remove_column :projects, :commit_count + end + + def down + add_column_with_default :projects, :repository_size, :float, default: 0.0 + add_column_with_default :projects, :commit_count, :integer, default: 0 + end +end diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb new file mode 100644 index 00000000000..809b09feb84 --- /dev/null +++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb @@ -0,0 +1,87 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveDotGitFromUsernames < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + include Gitlab::ShellAdapter + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + invalid_users.each do |user| + id = user['id'] + namespace_id = user['namespace_id'] + path_was = user['username'] + path_was_wildcard = quote_string("#{path_was}/%") + path = quote_string(rename_path(path_was)) + + move_namespace(namespace_id, path_was, path) + + execute "UPDATE routes SET path = '#{path}' WHERE source_type = 'Namespace' AND source_id = #{namespace_id}" + execute "UPDATE namespaces SET path = '#{path}' WHERE id = #{namespace_id}" + execute "UPDATE users SET username = '#{path}' WHERE id = #{id}" + + select_all("SELECT id, path FROM routes WHERE path LIKE '#{path_was_wildcard}'").each do |route| + new_path = "#{path}/#{route['path'].split('/').last}" + execute "UPDATE routes SET path = '#{new_path}' WHERE id = #{route['id']}" + end + end + end + + def down + # nothing to do here + end + + private + + def invalid_users + select_all("SELECT u.id, u.username, n.path AS namespace_path, n.id AS namespace_id FROM users u + INNER JOIN namespaces n ON n.owner_id = u.id + WHERE n.type is NULL AND n.path LIKE '%.git'") + end + + def route_exists?(path) + select_all("SELECT id, path FROM routes WHERE path = '#{quote_string(path)}'").present? + end + + # Accepts invalid path like test.git and returns test_git or + # test_git1 if test_git already taken + def rename_path(path) + # To stay closer with original name and reduce risk of duplicates + # we rename suffix instead of removing it + path = path.sub(/\.git\z/, '_git') + + counter = 0 + base = path + + while route_exists?(path) + counter += 1 + path = "#{base}#{counter}" + end + + path + end + + def move_namespace(namespace_id, path_was, path) + repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row| + Gitlab.config.repositories.storages[row['repository_storage']] + end.compact + + # Move the namespace directory in all storages paths used by member projects + repository_storage_paths.each do |repository_storage_path| + # Ensure old directory exists before moving it + gitlab_shell.add_namespace(repository_storage_path, path_was) + + unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path) + Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}" + + # if we cannot move namespace directory we should rollback + # db changes in order to prevent out of sync between db and fs + raise Exception.new('namespace directory cannot be moved') + end + end + + Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + end +end diff --git a/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb b/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb new file mode 100644 index 00000000000..50ad7437227 --- /dev/null +++ b/db/migrate/20161227192806_rename_slack_and_mattermost_notification_services.rb @@ -0,0 +1,25 @@ +class RenameSlackAndMattermostNotificationServices < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + update_column_in_batches(:services, :type, 'SlackService') do |table, query| + query.where(table[:type].eq('SlackNotificationService')) + end + + update_column_in_batches(:services, :type, 'MattermostService') do |table, query| + query.where(table[:type].eq('MattermostNotificationService')) + end + end + + def down + update_column_in_batches(:services, :type, 'SlackNotificationService') do |table, query| + query.where(table[:type].eq('SlackService')) + end + + update_column_in_batches(:services, :type, 'MattermostNotificationService') do |table, query| + query.where(table[:type].eq('MattermostService')) + end + end +end diff --git a/db/post_migrate/20161221140236_remove_unneeded_services.rb b/db/post_migrate/20161221140236_remove_unneeded_services.rb index a94ccc43a41..6b7e94c8641 100644 --- a/db/post_migrate/20161221140236_remove_unneeded_services.rb +++ b/db/post_migrate/20161221140236_remove_unneeded_services.rb @@ -4,6 +4,8 @@ class RemoveUnneededServices < ActiveRecord::Migration DOWNTIME = false def up + disable_statement_timeout + execute("DELETE FROM services WHERE active = false AND properties = '{}';") end diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb new file mode 100644 index 00000000000..7f7c2424a5c --- /dev/null +++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb @@ -0,0 +1,135 @@ +require 'thread' + +class RenameReservedProjectNames < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + include Gitlab::ShellAdapter + + DOWNTIME = false + + THREAD_COUNT = 8 + + KNOWN_PATHS = %w(.well-known + all + assets + blame + blob + commits + create + create_dir + edit + files + files + find_file + groups + hooks + issues + logs_tree + merge_requests + new + new + preview + profile + projects + public + raw + repository + robots.txt + s + snippets + teams + tree + u + unsubscribes + update + users + wikis) + + def up + queues = Array.new(THREAD_COUNT) { Queue.new } + start = false + + threads = Array.new(THREAD_COUNT) do |index| + Thread.new do + queue = queues[index] + + # Wait until we have input to process. + until start; end + + rename_projects(queue.pop) until queue.empty? + end + end + + enum = queues.each + + reserved_projects.each_slice(100) do |slice| + begin + queue = enum.next + rescue StopIteration + enum.rewind + retry + end + + queue << slice + end + + start = true + + threads.each(&:join) + end + + def down + # nothing to do here + end + + private + + def reserved_projects + Project.unscoped. + includes(:namespace). + where('EXISTS (SELECT 1 FROM namespaces WHERE projects.namespace_id = namespaces.id)'). + where('projects.path' => KNOWN_PATHS) + end + + def route_exists?(full_path) + quoted_path = ActiveRecord::Base.connection.quote_string(full_path) + + ActiveRecord::Base.connection. + select_all("SELECT id, path FROM routes WHERE path = '#{quoted_path}'").present? + end + + # Adds number to the end of the path that is not taken by other route + def rename_path(namespace_path, path_was) + counter = 0 + path = "#{path_was}#{counter}" + + while route_exists?("#{namespace_path}/#{path}") + counter += 1 + path = "#{path_was}#{counter}" + end + + path + end + + def rename_projects(projects) + projects.each do |project| + id = project.id + path_was = project.path + namespace_path = project.namespace.path + path = rename_path(namespace_path, path_was) + + begin + # Because project path update is quite complex operation we can't safely + # copy-paste all code from GitLab. As exception we use Rails code here + project.rename_repo if rename_project_row(project, path) + rescue Exception => e # rubocop: disable Lint/RescueException + Rails.logger.error "Exception when renaming project #{id}: #{e.message}" + end + end + end + + def rename_project_row(project, path) + project.respond_to?(:update_attributes) && + project.update_attributes(path: path) && + project.respond_to?(:rename_repo) + end +end diff --git a/db/schema.rb b/db/schema.rb index 05b6c807660..923ece86edb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20161221140236) do +ActiveRecord::Schema.define(version: 20161227192806) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -527,6 +527,7 @@ ActiveRecord::Schema.define(version: 20161221140236) do t.string "type" t.string "fingerprint" t.boolean "public", default: false, null: false + t.boolean "can_push", default: false, null: false end add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree @@ -901,6 +902,19 @@ ActiveRecord::Schema.define(version: 20161221140236) do add_index "project_import_data", ["project_id"], name: "index_project_import_data_on_project_id", using: :btree + create_table "project_statistics", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "namespace_id", null: false + t.integer "commit_count", limit: 8, default: 0, null: false + t.integer "storage_size", limit: 8, default: 0, null: false + t.integer "repository_size", limit: 8, default: 0, null: false + t.integer "lfs_objects_size", limit: 8, default: 0, null: false + t.integer "build_artifacts_size", limit: 8, default: 0, null: false + end + + add_index "project_statistics", ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree + add_index "project_statistics", ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree + create_table "projects", force: :cascade do |t| t.string "name" t.string "path" @@ -915,11 +929,9 @@ ActiveRecord::Schema.define(version: 20161221140236) do t.boolean "archived", default: false, null: false t.string "avatar" t.string "import_status" - t.float "repository_size", default: 0.0 t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 t.text "import_error" t.integer "ci_id" t.boolean "shared_runners_enabled", default: true, null: false @@ -1288,9 +1300,10 @@ ActiveRecord::Schema.define(version: 20161221140236) do add_foreign_key "personal_access_tokens", "users" add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade + add_foreign_key "project_statistics", "projects", on_delete: :cascade add_foreign_key "protected_branch_merge_access_levels", "protected_branches" add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" -end +end
\ No newline at end of file diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md index 3ba8387c7f0..cca422892ec 100644 --- a/doc/administration/build_artifacts.md +++ b/doc/administration/build_artifacts.md @@ -88,3 +88,9 @@ artifacts through the [Admin area settings](../user/admin_area/settings/continuo [reconfigure gitlab]: restart_gitlab.md "How to restart GitLab" [restart gitlab]: restart_gitlab.md "How to restart GitLab" + +## Storage statistics + +You can see the total storage used for build artifacts on groups and projects +in the administration area, as well as through the [groups](../api/groups.md) +and [projects APIs](../api/projects.md). diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md index 5f248ab6f91..284d5f88c55 100644 --- a/doc/api/deploy_keys.md +++ b/doc/api/deploy_keys.md @@ -20,12 +20,14 @@ Example response: "id": 1, "title": "Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "can_push": false, "created_at": "2013-10-02T10:12:29Z" }, { "id": 3, "title": "Another Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "can_push": true, "created_at": "2013-10-02T11:12:29Z" } ] @@ -55,12 +57,14 @@ Example response: "id": 1, "title": "Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "can_push": false, "created_at": "2013-10-02T10:12:29Z" }, { "id": 3, "title": "Another Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "can_push": false, "created_at": "2013-10-02T11:12:29Z" } ] @@ -92,6 +96,7 @@ Example response: "id": 1, "title": "Public key", "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "can_push": false, "created_at": "2013-10-02T10:12:29Z" } ``` @@ -107,14 +112,15 @@ project only if original one was is accessible by the same user. POST /projects/:id/deploy_keys ``` -| Attribute | Type | Required | Description | -| --------- | ---- | -------- | ----------- | -| `id` | integer | yes | The ID of the project | -| `title` | string | yes | New deploy key's title | -| `key` | string | yes | New deploy key | +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the project | +| `title` | string | yes | New deploy key's title | +| `key` | string | yes | New deploy key | +| `can_push` | boolean | no | Can deploy key push to the project's repository | ```bash -curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA..."}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/" +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --header "Content-Type: application/json" --data '{"title": "My deploy key", "key": "ssh-rsa AAAA...", "can_push": "true"}' "https://gitlab.example.com/api/v3/projects/5/deploy_keys/" ``` Example response: @@ -124,6 +130,7 @@ Example response: "key" : "ssh-rsa AAAA...", "id" : 12, "title" : "My deploy key", + "can_push": true, "created_at" : "2015-08-29T12:44:31.550Z" } ``` diff --git a/doc/api/groups.md b/doc/api/groups.md index 134d7bda22f..bc737bff8ee 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -13,6 +13,7 @@ Parameters: | `search` | string | no | Return list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | +| `statistics` | boolean | no | Include group statistics (admins only) | ``` GET /groups @@ -31,7 +32,6 @@ GET /groups You can search for groups by name or path, see below. -======= ## List owned groups Get a list of groups which are owned by the authenticated user. @@ -40,6 +40,12 @@ Get a list of groups which are owned by the authenticated user. GET /groups/owned ``` +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `statistics` | boolean | no | Include group statistics | + ## List a group's projects Get a list of projects in this group. diff --git a/doc/api/projects.md b/doc/api/projects.md index edffad555a5..122075bbd11 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -307,6 +307,8 @@ Parameters: | `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of authorized projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | +| `statistics` | boolean | no | Include project statistics | ### List starred projects @@ -325,6 +327,7 @@ Parameters: | `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of authorized projects matching the search criteria | +| `simple` | boolean | no | Return only the ID, URL, name, and path of each project | ### List ALL projects @@ -343,6 +346,7 @@ Parameters: | `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at` | | `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Return list of authorized projects matching the search criteria | +| `statistics` | boolean | no | Include project statistics | ### Get single project diff --git a/doc/api/settings.md b/doc/api/settings.md index 218546aafea..0bd38a6e664 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -79,6 +79,7 @@ PUT /application/settings | `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. | | `koding_enabled` | boolean | no | Enable Koding integration. Default is `false`. | | `koding_url` | string | yes (if `koding_enabled` is `true`) | The Koding instance URL for integration. | +| `disabled_oauth_sign_in_sources` | Array of strings | no | Disabled OAuth sign-in sources | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/application/settings?signup_enabled=false&default_project_visibility=1 diff --git a/doc/ci/README.md b/doc/ci/README.md index 6a9495f8892..dd14698e9cd 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -23,7 +23,7 @@ - [CI/CD pipelines settings](../user/project/pipelines/settings.md) - [Review Apps](review_apps/index.md) - [Git submodules](git_submodules.md) Using Git submodules in your CI jobs -- [Autodeploy](autodeploy/index.md) +- [Auto deploy](autodeploy/index.md) ## Breaking changes diff --git a/doc/ci/autodeploy/img/auto_deploy_button.png b/doc/ci/autodeploy/img/auto_deploy_button.png Binary files differnew file mode 100644 index 00000000000..423e76a6cda --- /dev/null +++ b/doc/ci/autodeploy/img/auto_deploy_button.png diff --git a/doc/ci/autodeploy/img/auto_deploy_dropdown.png b/doc/ci/autodeploy/img/auto_deploy_dropdown.png Binary files differnew file mode 100644 index 00000000000..957870ec8c7 --- /dev/null +++ b/doc/ci/autodeploy/img/auto_deploy_dropdown.png diff --git a/doc/ci/autodeploy/img/autodeploy_button.png b/doc/ci/autodeploy/img/autodeploy_button.png Binary files differdeleted file mode 100644 index 9e2cd57a0ba..00000000000 --- a/doc/ci/autodeploy/img/autodeploy_button.png +++ /dev/null diff --git a/doc/ci/autodeploy/img/autodeploy_dropdown.png b/doc/ci/autodeploy/img/autodeploy_dropdown.png Binary files differdeleted file mode 100644 index d2733de83df..00000000000 --- a/doc/ci/autodeploy/img/autodeploy_dropdown.png +++ /dev/null diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md index 9c79d8c457e..630207ffa09 100644 --- a/doc/ci/autodeploy/index.md +++ b/doc/ci/autodeploy/index.md @@ -1,8 +1,8 @@ -# Autodeploy +# Auto deploy > [Introduced][mr-8135] in GitLab 8.15. -Autodeploy is an easy way to configure GitLab CI for the deployment of your +Auto deploy is an easy way to configure GitLab CI for the deployment of your application. GitLab Community maintains a list of `.gitlab-ci.yml` templates for various infrastructure providers and deployment scripts powering them. These scripts are responsible for packaging your application, @@ -15,7 +15,7 @@ deployment. ## Supported templates -The list of supported autodeploy templates is available [here][autodeploy-templates]. +The list of supported auto deploy templates is available [here][auto-deploy-templates]. ## Configuration @@ -24,17 +24,17 @@ credentials. For example, if you want to deploy to OpenShift you have to enable [Kubernetes service][kubernetes-service]. 1. Configure GitLab Runner to use Docker or Kubernetes executor with [privileged mode enabled][docker-in-docker]. -1. Navigate to the "Project" tab and click "Set up autodeploy" button. - ![Autodeploy button](img/autodeploy_button.png) +1. Navigate to the "Project" tab and click "Set up auto deploy" button. + ![Auto deploy button](img/auto_deploy_button.png) 1. Select a template. - ![Dropdown with autodeploy templates](img/autodeploy_dropdown.png) + ![Dropdown with auto deploy templates](img/auto_deploy_dropdown.png) 1. Commit your changes and create a merge request. 1. Test your deployment configuration using a [Review App][review-app] that was created automatically for you. [mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135 [project-services]: ../../project_services/project_services.md -[autodeploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy +[auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy [kubernetes-service]: ../../project_services/kubernetes.md [docker-in-docker]: ../docker/using_docker_build.md#use-docker-in-docker-executor [review-app]: ../review_apps/index.md diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md index cea11e5f126..d6d770e27c1 100644 --- a/doc/development/sidekiq_debugging.md +++ b/doc/development/sidekiq_debugging.md @@ -3,12 +3,15 @@ ## Log arguments to Sidekiq jobs If you want to see what arguments are being passed to Sidekiq jobs you can set -the SIDEKIQ_LOG_ARGUMENTS environment variable. +the `SIDEKIQ_LOG_ARGUMENTS` [environment variable] +(https://docs.gitlab.com/omnibus/settings/environment-variables.html) to `1` (true). + +Example: ``` -SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start +gitlab_rails['env'] = {"SIDEKIQ_LOG_ARGUMENTS" => "1"} ``` -It is not recommend to enable this setting in production because some Sidekiq -jobs (such as sending a password reset email) take secret arguments (for -example the password reset token). +Please note: It is not recommend to enable this setting in production because some +Sidekiq jobs (such as sending a password reset email) take secret arguments (for +example the password reset token).
\ No newline at end of file diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md index daeb15460c2..903e54bf9dc 100644 --- a/doc/development/ux_guide/animation.md +++ b/doc/development/ux_guide/animation.md @@ -39,4 +39,19 @@ When information is updating in place, a quick, subtle animation is needed. The ![Quick update animation](img/animation-quickupdate.gif) -> TODO: Add guidance for other kinds of animation
\ No newline at end of file +### Moving transitions + +When elements move on screen, there should be a quick animation so it is clear to users what moved where. The timing of this animation differs based on the amount of movement and change. Consider animations between `200ms` and `400ms`. + +#### Shifting elements on reorder +An example of a moving transition is when elements have to move out of the way when you drag an element. + +View the [interactive example](http://codepen.io/awhildy/full/ALyKPE/) here. + +![Reorder animation](img/animation-reorder.gif) + +#### Autoscroll the page +Another example of a moving transition is when you have to autoscroll the page to keep an active element visible. + +View the [interactive example](http://codepen.io/awhildy/full/PbxgVo/) here. +![Autoscroll animation](img/animation-autoscroll.gif)
\ No newline at end of file diff --git a/doc/development/ux_guide/basics.md b/doc/development/ux_guide/basics.md index e81729556d8..76ec7fd466b 100644 --- a/doc/development/ux_guide/basics.md +++ b/doc/development/ux_guide/basics.md @@ -50,13 +50,13 @@ GitLab uses Font Awesome icons throughout our interface. ## Color -| | | -| :------: | :------- | -| ![Blue](img/color-blue.png) | Blue is used to highlight primary active elements (such as the current tab), as well as other organizational and managing commands.| -| ![Green](img/color-green.png) | Green is for actions that create new objects. | -| ![Orange](img/color-orange.png) | Orange is used for warnings | -| ![Red](img/color-red.png) | Red is reserved for delete and other destructive commands | -| ![Grey](img/color-grey.png) | Grey is used for neutral secondary elements. Depending on context, white is sometimes used instead. | +| | State | Action | +| :------: | :------- | :------- | +| ![Blue](img/color-blue.png) | Primary and active (such as the current tab) | Organizational, managing, and retry commands| +| ![Green](img/color-green.png) | Opened | Create new objects | +| ![Orange](img/color-orange.png) | Warning | Non destructive action | +| ![Red](img/color-red.png) | Closed | Delete and other destructive commands | +| ![Grey](img/color-grey.png) | Neutral | Neutral secondary commands | > TODO: Establish a perspective for color in terms of our personality and rationalize with Marketing usage. diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md index 8896d450f14..e48897426cb 100644 --- a/doc/development/ux_guide/copy.md +++ b/doc/development/ux_guide/copy.md @@ -101,3 +101,11 @@ The form should be titled `Edit issue`. The submit button should be labeled `Sav | Approve | Approve an open merge request || | Remove approval, unapproved | Remove approval of an open merge request | Do not use `unapprove` as that is not an English word| | Merge | Merge an open merge request || + +### Comments & Discussions + +#### Comment +A **comment** is a written piece of text that users of GitLab can create. Comments have the meta data of author and time stamp. Comments can be added in a variety of contexts, such as issues, merge requests, and discussions. + +#### Dicussion +A **discussion** is a group of 1 or more comments. A discussion can include sub discussions. Some discussions have the special capability of being able to be **resolved**. Both the comments in the discussion and the discussion itself can be resolved.
\ No newline at end of file diff --git a/doc/development/ux_guide/img/animation-autoscroll.gif b/doc/development/ux_guide/img/animation-autoscroll.gif Binary files differnew file mode 100644 index 00000000000..155b0234c64 --- /dev/null +++ b/doc/development/ux_guide/img/animation-autoscroll.gif diff --git a/doc/development/ux_guide/img/animation-reorder.gif b/doc/development/ux_guide/img/animation-reorder.gif Binary files differnew file mode 100644 index 00000000000..ccdeb3d396f --- /dev/null +++ b/doc/development/ux_guide/img/animation-reorder.gif diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index b3c73e947f0..5f6a718135d 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -40,6 +40,12 @@ In `config/gitlab.yml`: storage_path: /mnt/storage/lfs-objects ``` +## Storage statistics + +You can see the total storage used for LFS objects on groups and projects +in the administration area, as well as through the [groups](../api/groups.md) +and [projects APIs](../api/projects.md). + ## Known limitations * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) @@ -47,3 +53,5 @@ In `config/gitlab.yml`: * Currently, removing LFS objects from GitLab Git LFS storage is not supported * LFS authentications via SSH was added with GitLab 8.12 * Only compatible with the GitLFS client versions 1.1.0 and up, or 1.0.2. +* The storage statistics currently count each LFS object multiple times for + every project linking to it diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature new file mode 100644 index 00000000000..95ac77cddd2 --- /dev/null +++ b/features/admin/deploy_keys.feature @@ -0,0 +1,23 @@ +@admin +Feature: Admin Deploy Keys + Background: + Given I sign in as an admin + And there are public deploy keys in system + + Scenario: Deploy Keys list + When I visit admin deploy keys page + Then I should see all public deploy keys + + Scenario: Deploy Keys new + When I visit admin deploy keys page + And I click 'New Deploy Key' + And I submit new deploy key + Then I should be on admin deploy keys page + And I should see newly created deploy key without write access + + Scenario: Deploy Keys new with write access + When I visit admin deploy keys page + And I click 'New Deploy Key' + And I submit new deploy key with write access + Then I should be on admin deploy keys page + And I should see newly created deploy key with write access diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb new file mode 100644 index 00000000000..79312a5d1c5 --- /dev/null +++ b/features/steps/admin/deploy_keys.rb @@ -0,0 +1,59 @@ +class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'there are public deploy keys in system' do + create(:deploy_key, public: true) + create(:another_deploy_key, public: true) + end + + step 'I should see all public deploy keys' do + DeployKey.are_public.each do |p| + expect(page).to have_content p.title + end + end + + step 'I visit admin deploy key page' do + visit admin_deploy_key_path(deploy_key) + end + + step 'I visit admin deploy keys page' do + visit admin_deploy_keys_path + end + + step 'I click \'New Deploy Key\'' do + click_link 'New Deploy Key' + end + + step 'I submit new deploy key' do + fill_in "deploy_key_title", with: "laptop" + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" + click_button "Create" + end + + step 'I submit new deploy key with write access' do + fill_in "deploy_key_title", with: "server" + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" + check "deploy_key_can_push" + click_button "Create" + end + + step 'I should be on admin deploy keys page' do + expect(current_path).to eq admin_deploy_keys_path + end + + step 'I should see newly created deploy key without write access' do + expect(page).to have_content(deploy_key.title) + expect(page).to have_content('No') + end + + step 'I should see newly created deploy key with write access' do + expect(page).to have_content(deploy_key.title) + expect(page).to have_content('Yes') + end + + def deploy_key + @deploy_key ||= DeployKey.are_public.first + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index dfbb3ab86dd..d2fadf6a3d0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -78,21 +78,21 @@ module API expose :container_registry_enabled # Expose old field names with the new permissions methods to keep API compatible - expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:user]) } - expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:user]) } - expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:user]) } - expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:user]) } - expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:user]) } + expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) } + expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) } + expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) } + expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) } + expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) } expose :created_at, :last_activity_at expose :shared_runners_enabled expose :lfs_enabled?, as: :lfs_enabled expose :creator_id - expose :namespace + expose :namespace, using: 'API::Entities::Namespace' expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? } expose :avatar_url expose :star_count, :forks_count - expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:user]) && project.default_issues_tracker? } + expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? } expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] } expose :public_builds expose :shared_with_groups do |project, options| @@ -101,6 +101,16 @@ module API expose :only_allow_merge_if_build_succeeds expose :request_access_enabled expose :only_allow_merge_if_all_discussions_are_resolved + + expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics + end + + class ProjectStatistics < Grape::Entity + expose :commit_count + expose :storage_size + expose :repository_size + expose :lfs_objects_size + expose :build_artifacts_size end class Member < UserBasic @@ -127,6 +137,15 @@ module API expose :avatar_url expose :web_url expose :request_access_enabled + + expose :statistics, if: :statistics do + with_options format_with: -> (value) { value.to_i } do + expose :storage_size + expose :repository_size + expose :lfs_objects_size + expose :build_artifacts_size + end + end end class GroupDetail < Group @@ -298,7 +317,7 @@ module API end class SSHKey < Grape::Entity - expose :id, :title, :key, :created_at + expose :id, :title, :key, :created_at, :can_push end class SSHKeyWithUser < SSHKey @@ -391,7 +410,7 @@ module API end class Namespace < Grape::Entity - expose :id, :path, :kind + expose :id, :name, :path, :kind end class MemberAccess < Grape::Entity @@ -440,12 +459,12 @@ module API class ProjectWithAccess < Project expose :permissions do expose :project_access, using: Entities::ProjectAccess do |project, options| - project.project_members.find_by(user_id: options[:user].id) + project.project_members.find_by(user_id: options[:current_user].id) end expose :group_access, using: Entities::GroupAccess do |project, options| if project.group - project.group.group_members.find_by(user_id: options[:user].id) + project.group.group_members.find_by(user_id: options[:current_user].id) end end end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 9b9d3df7435..e04d2e40fb6 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -11,6 +11,20 @@ module API optional :lfs_enabled, type: Boolean, desc: 'Enable/disable LFS for the projects in this group' optional :request_access_enabled, type: Boolean, desc: 'Allow users to request member access' end + + params :statistics_params do + optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' + end + + def present_groups(groups, options = {}) + options = options.reverse_merge( + with: Entities::Group, + current_user: current_user, + ) + + groups = groups.with_statistics if options[:statistics] + present paginate(groups), options + end end resource :groups do @@ -18,6 +32,7 @@ module API success Entities::Group end params do + use :statistics_params optional :skip_groups, type: Array[Integer], desc: 'Array of group ids to exclude from list' optional :all_available, type: Boolean, desc: 'Show all group that you have access to' optional :search, type: String, desc: 'Search for a specific group' @@ -38,7 +53,7 @@ module API groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? groups = groups.reorder(params[:order_by] => params[:sort]) - present paginate(groups), with: Entities::Group + present_groups groups, statistics: params[:statistics] && current_user.is_admin? end desc 'Get list of owned groups for authenticated user' do @@ -46,10 +61,10 @@ module API end params do use :pagination + use :statistics_params end get '/owned' do - groups = current_user.owned_groups - present paginate(groups), with: Entities::Group, user: current_user + present_groups current_user.owned_groups, statistics: params[:statistics] end desc 'Create a group. Available only for users who can create groups.' do @@ -66,7 +81,7 @@ module API group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute if group.persisted? - present group, with: Entities::Group + present group, with: Entities::Group, current_user: current_user else render_api_error!("Failed to save group #{group.errors.messages}", 400) end @@ -92,7 +107,7 @@ module API authorize! :admin_group, group if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute - present group, with: Entities::GroupDetail + present group, with: Entities::GroupDetail, current_user: current_user else render_validation_error!(group) end @@ -103,7 +118,7 @@ module API end get ":id" do group = find_group!(params[:id]) - present group, with: Entities::GroupDetail + present group, with: Entities::GroupDetail, current_user: current_user end desc 'Remove a group.' @@ -134,7 +149,7 @@ module API projects = GroupProjectsFinder.new(group).execute(current_user) projects = filter_projects(projects) entity = params[:simple] ? Entities::BasicProjectDetails : Entities::Project - present paginate(projects), with: entity, user: current_user + present paginate(projects), with: entity, current_user: current_user end desc 'Transfer a project to the group namespace. Available only for admin.' do @@ -150,7 +165,7 @@ module API result = ::Projects::TransferService.new(project, current_user).execute(group) if result - present group, with: Entities::GroupDetail + present group, with: Entities::GroupDetail, current_user: current_user else render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 106694e13df..20b5bc1502a 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -91,7 +91,7 @@ module API end def authenticate_non_get! - authenticate! unless %w[GET HEAD].include?(route.route_method) + authenticate! unless %w[GET HEAD].include?(route.request_method) end def authenticate_by_gitlab_shell_token! @@ -243,7 +243,7 @@ module API rack_response({ 'message' => '500 Internal Server Error' }.to_json, 500) end - # Projects helpers + # project helpers def filter_projects(projects) if params[:search].present? diff --git a/lib/api/notes.rb b/lib/api/notes.rb index d0faf17714b..284e4cf549a 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -69,8 +69,6 @@ module API optional :created_at, type: String, desc: 'The creation date of the note' end post ":id/#{noteables_str}/:noteable_id/notes" do - required_attributes! [:body] - opts = { note: params[:body], noteable_type: noteables_str.classify, diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 2929d2157dc..3be14e8eb76 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -40,6 +40,15 @@ module API resource :projects do helpers do + params :collection_params do + use :sort_params + use :filter_params + use :pagination + + optional :simple, type: Boolean, default: false, + desc: 'Return only the ID, URL, name, and path of each project' + end + params :sort_params do optional :order_by, type: String, values: %w[id name path created_at updated_at last_activity_at], default: 'created_at', desc: 'Return projects ordered by field' @@ -52,97 +61,94 @@ module API optional :visibility, type: String, values: %w[public internal private], desc: 'Limit by visibility' optional :search, type: String, desc: 'Return list of authorized projects matching the search criteria' - use :sort_params + end + + params :statistics_params do + optional :statistics, type: Boolean, default: false, desc: 'Include project statistics' end params :create_params do optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.' optional :import_url, type: String, desc: 'URL from which the project is imported' end + + def present_projects(projects, options = {}) + options = options.reverse_merge( + with: Entities::Project, + current_user: current_user, + simple: params[:simple], + ) + + projects = filter_projects(projects) + projects = projects.with_statistics if options[:statistics] + options[:with] = Entities::BasicProjectDetails if options[:simple] + + present paginate(projects), options + end end desc 'Get a list of visible projects for authenticated user' do success Entities::BasicProjectDetails end params do - optional :simple, type: Boolean, default: false, - desc: 'Return only the ID, URL, name, and path of each project' - use :filter_params - use :pagination + use :collection_params end get '/visible' do - projects = ProjectsFinder.new.execute(current_user) - projects = filter_projects(projects) - entity = params[:simple] || !current_user ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - - present paginate(projects), with: entity, user: current_user + entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails + present_projects ProjectsFinder.new.execute(current_user), with: entity end desc 'Get a projects list for authenticated user' do success Entities::BasicProjectDetails end params do - optional :simple, type: Boolean, default: false, - desc: 'Return only the ID, URL, name, and path of each project' - use :filter_params - use :pagination + use :collection_params end get do authenticate! - projects = current_user.authorized_projects - projects = filter_projects(projects) - entity = params[:simple] ? Entities::BasicProjectDetails : Entities::ProjectWithAccess - - present paginate(projects), with: entity, user: current_user + present_projects current_user.authorized_projects, + with: Entities::ProjectWithAccess end desc 'Get an owned projects list for authenticated user' do success Entities::BasicProjectDetails end params do - use :filter_params - use :pagination + use :collection_params + use :statistics_params end get '/owned' do authenticate! - projects = current_user.owned_projects - projects = filter_projects(projects) - - present paginate(projects), with: Entities::ProjectWithAccess, user: current_user + present_projects current_user.owned_projects, + with: Entities::ProjectWithAccess, + statistics: params[:statistics] end desc 'Gets starred project for the authenticated user' do success Entities::BasicProjectDetails end params do - use :filter_params - use :pagination + use :collection_params end get '/starred' do authenticate! - projects = current_user.viewable_starred_projects - projects = filter_projects(projects) - - present paginate(projects), with: Entities::Project, user: current_user + present_projects current_user.viewable_starred_projects end desc 'Get all projects for admin user' do success Entities::BasicProjectDetails end params do - use :filter_params - use :pagination + use :collection_params + use :statistics_params end get '/all' do authenticated_as_admin! - projects = Project.all - projects = filter_projects(projects) - - present paginate(projects), with: Entities::ProjectWithAccess, user: current_user + present_projects Project.all, with: Entities::ProjectWithAccess, statistics: params[:statistics] end desc 'Search for projects the current user has access to' do @@ -221,7 +227,7 @@ module API end get ":id" do entity = current_user ? Entities::ProjectWithAccess : Entities::BasicProjectDetails - present user_project, with: entity, user: current_user, + present user_project, with: entity, current_user: current_user, user_can_admin_project: can?(current_user, :admin_project, user_project) end diff --git a/lib/api/settings.rb b/lib/api/settings.rb index c4cb1c7924a..9eb9a105bde 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -9,23 +9,117 @@ module API end end - # Get current applicaiton settings - # - # Example Request: - # GET /application/settings + desc 'Get the current application settings' do + success Entities::ApplicationSetting + end get "application/settings" do present current_settings, with: Entities::ApplicationSetting end - # Modify application settings - # - # Example Request: - # PUT /application/settings + desc 'Modify application settings' do + success Entities::ApplicationSetting + end + params do + optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master' + optional :default_project_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default project visibility' + optional :default_snippet_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default snippet visibility' + optional :default_group_visibility, type: Integer, values: Gitlab::VisibilityLevel.values, desc: 'The default group visibility' + optional :restricted_visibility_levels, type: Array[String], desc: 'Selected levels cannot be used by non-admin users for projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.' + optional :import_sources, type: Array[String], values: %w[github bitbucket gitlab google_code fogbugz git gitlab_project], + desc: 'Enabled sources for code import during project creation. OmniAuth must be configured for GitHub, Bitbucket, and GitLab.com' + optional :disabled_oauth_sign_in_sources, type: Array[String], desc: 'Disable certain OAuth sign-in sources' + optional :enabled_git_access_protocol, type: String, values: %w[ssh http nil], desc: 'Allow only the selected protocols to be used for Git access.' + optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled' + optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects' + optional :max_attachment_size, type: Integer, desc: 'Maximum attachment size in MB' + optional :session_expire_delay, type: Integer, desc: 'Session duration in minutes. GitLab restart is required to apply changes.' + optional :user_oauth_applications, type: Boolean, desc: 'Allow users to register any application to use GitLab as an OAuth provider' + optional :user_default_external, type: Boolean, desc: 'Newly registered users will by default be external' + optional :signup_enabled, type: Boolean, desc: 'Flag indicating if sign up is enabled' + optional :send_user_confirmation_email, type: Boolean, desc: 'Send confirmation email on sign-up' + optional :domain_whitelist, type: String, desc: 'ONLY users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + optional :domain_blacklist_enabled, type: Boolean, desc: 'Enable domain blacklist for sign ups' + given domain_blacklist_enabled: ->(val) { val } do + requires :domain_blacklist, type: String, desc: 'Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com' + end + optional :after_sign_up_text, type: String, desc: 'Text shown after sign up' + optional :signin_enabled, type: Boolean, desc: 'Flag indicating if sign in is enabled' + optional :require_two_factor_authentication, type: Boolean, desc: 'Require all users to setup Two-factor authentication' + given require_two_factor_authentication: ->(val) { val } do + requires :two_factor_grace_period, type: Integer, desc: 'Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication' + end + optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page' + optional :after_sign_out_path, type: String, desc: 'We will redirect users to this page after they sign out' + optional :sign_in_text, type: String, desc: 'The sign in text of the GitLab application' + optional :help_page_text, type: String, desc: 'Custom text displayed on the help page' + optional :shared_runners_enabled, type: Boolean, desc: 'Enable shared runners for new projects' + given shared_runners_enabled: ->(val) { val } do + requires :shared_runners_text, type: String, desc: 'Shared runners text ' + end + optional :max_artifacts_size, type: Integer, desc: "Set the maximum file size each build's artifacts can have" + optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' + optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' + given metrics_enabled: ->(val) { val } do + requires :metrics_host, type: String, desc: 'The InfluxDB host' + requires :metrics_port, type: Integer, desc: 'The UDP port to use for connecting to InfluxDB' + requires :metrics_pool_size, type: Integer, desc: 'The amount of InfluxDB connections to open' + requires :metrics_timeout, type: Integer, desc: 'The amount of seconds after which an InfluxDB connection will time out' + requires :metrics_method_call_threshold, type: Integer, desc: 'A method call is only tracked when it takes longer to complete than the given amount of milliseconds.' + requires :metrics_sample_interval, type: Integer, desc: 'The sampling interval in seconds' + requires :metrics_packet_size, type: Integer, desc: 'The amount of points to store in a single UDP packet' + end + optional :sidekiq_throttling_enabled, type: Boolean, desc: 'Enable Sidekiq Job Throttling' + given sidekiq_throttling_enabled: ->(val) { val } do + requires :sidekiq_throttling_queus, type: Array[String], desc: 'Choose which queues you wish to throttle' + requires :sidekiq_throttling_factor, type: Float, desc: 'The factor by which the queues should be throttled. A value between 0.0 and 1.0, exclusive.' + end + optional :recaptcha_enabled, type: Boolean, desc: 'Helps prevent bots from creating accounts' + given recaptcha_enabled: ->(val) { val } do + requires :recaptcha_site_key, type: String, desc: 'Generate site key at http://www.google.com/recaptcha' + requires :recaptcha_private_key, type: String, desc: 'Generate private key at http://www.google.com/recaptcha' + end + optional :akismet_enabled, type: Boolean, desc: 'Helps prevent bots from creating issues' + given akismet_enabled: ->(val) { val } do + requires :akismet_api_key, type: String, desc: 'Generate API key at http://www.akismet.com' + end + optional :admin_notification_email, type: String, desc: 'Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.' + optional :sentry_enabled, type: Boolean, desc: 'Sentry is an error reporting and logging tool which is currently not shipped with GitLab, get it here: https://getsentry.com' + given sentry_enabled: ->(val) { val } do + requires :sentry_dsn, type: String, desc: 'Sentry Data Source Name' + end + optional :repository_storage, type: String, desc: 'Storage paths for new projects' + optional :repository_checks_enabled, type: Boolean, desc: "GitLab will periodically run 'git fsck' in all project and wiki repositories to look for silent disk corruption issues." + optional :koding_enabled, type: Boolean, desc: 'Enable Koding' + given koding_enabled: ->(val) { val } do + requires :koding_url, type: String, desc: 'The Koding team URL' + end + optional :version_check_enabled, type: Boolean, desc: 'Let GitLab inform you when an update is available.' + optional :email_author_in_body, type: Boolean, desc: 'Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.' + optional :html_emails_enabled, type: Boolean, desc: 'By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.' + optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)' + given housekeeping_enabled: ->(val) { val } do + requires :housekeeping_bitmaps_enabled, type: Boolean, desc: "Creating pack file bitmaps makes housekeeping take a little longer but bitmaps should accelerate 'git clone' performance." + requires :housekeeping_incremental_repack_period, type: Integer, desc: "Number of Git pushes after which an incremental 'git repack' is run." + requires :housekeeping_full_repack_period, type: Integer, desc: "Number of Git pushes after which a full 'git repack' is run." + requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run." + end + at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility, + :default_group_visibility, :restricted_visibility_levels, :import_sources, + :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, + :max_attachment_size, :session_expire_delay, :disabled_oauth_sign_in_sources, + :user_oauth_applications, :user_default_external, :signup_enabled, + :send_user_confirmation_email, :domain_whitelist, :domain_blacklist_enabled, + :after_sign_up_text, :signin_enabled, :require_two_factor_authentication, + :home_page_url, :after_sign_out_path, :sign_in_text, :help_page_text, + :shared_runners_enabled, :max_artifacts_size, :container_registry_token_expire_delay, + :metrics_enabled, :sidekiq_throttling_enabled, :recaptcha_enabled, + :akismet_enabled, :admin_notification_email, :sentry_enabled, + :repository_storage, :repository_checks_enabled, :koding_enabled, + :version_check_enabled, :email_author_in_body, :html_emails_enabled, + :housekeeping_enabled + end put "application/settings" do - attributes = ["repository_storage"] + current_settings.attributes.keys - ["id"] - attrs = attributes_for_keys(attributes) - - if current_settings.update_attributes(attrs) + if current_settings.update_attributes(declared_params(include_missing: false)) present current_settings, with: Entities::ApplicationSetting else render_validation_error!(current_settings) diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb index 6be7f690676..39b86c61a18 100644 --- a/lib/gitlab/auth/result.rb +++ b/lib/gitlab/auth/result.rb @@ -9,8 +9,7 @@ module Gitlab def lfs_deploy_token?(for_project) type == :lfs_deploy_token && - actor && - actor.projects.include?(for_project) + actor.try(:has_access_to?, for_project) end def success? diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 3d203017d9f..9c391fa92a3 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -1,14 +1,16 @@ module Gitlab module Checks class ChangeAccess - attr_reader :user_access, :project + attr_reader :user_access, :project, :skip_authorization - def initialize(change, user_access:, project:, env: {}) + def initialize( + change, user_access:, project:, env: {}, skip_authorization: false) @oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref) @branch_name = Gitlab::Git.branch_name(@ref) @user_access = user_access @project = project @env = env + @skip_authorization = skip_authorization end def exec @@ -24,6 +26,7 @@ module Gitlab protected def protected_branch_checks + return if skip_authorization return unless @branch_name return unless project.protected_branch?(@branch_name) @@ -49,6 +52,8 @@ module Gitlab end def tag_checks + return if skip_authorization + tag_ref = Gitlab::Git.tag_name(@ref) if tag_ref && protected_tag?(tag_ref) && user_access.cannot_do_action?(:admin_project) @@ -57,6 +62,8 @@ module Gitlab end def push_checks + return if skip_authorization + if user_access.cannot_do_action?(:push_code) "You are not allowed to push code to this project." end diff --git a/lib/gitlab/diff/file_collection/merge_request_diff.rb b/lib/gitlab/diff/file_collection/merge_request_diff.rb index 56530448f36..329d12f13d1 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff.rb @@ -61,7 +61,10 @@ module Gitlab end def cacheable?(diff_file) - @merge_request_diff.present? && diff_file.blob && diff_file.blob.text? + @merge_request_diff.present? && + diff_file.blob && + diff_file.blob.text? && + @project.repository.diffable?(diff_file.blob) end def cache_key diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index f586c5ab062..8c8dd1b9cef 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -13,9 +13,17 @@ module Gitlab encoding = body.encoding - body = discourse_email_trimmer(body) + body = EmailReplyTrimmer.trim(body) - body = EmailReplyParser.parse_reply(body) + return '' unless body + + # not using /\s+$/ here because that deletes empty lines + body = body.gsub(/[ \t]$/, '') + + # NOTE: We currently don't support empty quotes. + # EmailReplyTrimmer allows this as a special case, + # so we detect it manually here. + return "" if body.lines.all? { |l| l.strip.empty? || l.start_with?('>') } body.force_encoding(encoding).encode("UTF-8") end @@ -57,30 +65,6 @@ module Gitlab rescue nil end - - REPLYING_HEADER_LABELS = %w(From Sent To Subject Reply To Cc Bcc Date) - REPLYING_HEADER_REGEX = Regexp.union(REPLYING_HEADER_LABELS.map { |label| "#{label}:" }) - - def discourse_email_trimmer(body) - lines = body.scrub.lines.to_a - range_end = 0 - - lines.each_with_index do |l, idx| - # This one might be controversial but so many reply lines have years, times and end with a colon. - # Let's try it and see how well it works. - break if (l =~ /\d{4}/ && l =~ /\d:\d\d/ && l =~ /\:$/) || - (l =~ /On \w+ \d+,? \d+,?.*wrote:/) - - # Headers on subsequent lines - break if (0..2).all? { |off| lines[idx + off] =~ REPLYING_HEADER_REGEX } - # Headers on the same line - break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3 - - range_end = idx - end - - lines[0..range_end].join.strip - end end end end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index c6b6efda360..7e1484613f2 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -7,7 +7,8 @@ module Gitlab ERROR_MESSAGES = { upload: 'You are not allowed to upload code for this project.', download: 'You are not allowed to download code from this project.', - deploy_key: 'Deploy keys are not allowed to push code.', + deploy_key_upload: + 'This deploy key does not have write access to this project.', no_repo: 'A repository for this project does not exist yet.' } @@ -31,12 +32,13 @@ module Gitlab check_active_user! check_project_accessibility! check_command_existence!(cmd) + check_repository_existence! case cmd when *DOWNLOAD_COMMANDS - download_access_check + check_download_access! when *PUSH_COMMANDS - push_access_check(changes) + check_push_access!(changes) end build_status_object(true) @@ -44,32 +46,10 @@ module Gitlab build_status_object(false, ex.message) end - def download_access_check - if user - user_download_access_check - elsif deploy_key.nil? && !guest_can_downlod_code? - raise UnauthorizedError, ERROR_MESSAGES[:download] - end - end - - def push_access_check(changes) - if user - user_push_access_check(changes) - else - raise UnauthorizedError, ERROR_MESSAGES[deploy_key ? :deploy_key : :upload] - end - end - - def guest_can_downlod_code? + def guest_can_download_code? Guest.can?(:download_code, project) end - def user_download_access_check - unless user_can_download_code? || build_can_download_code? - raise UnauthorizedError, ERROR_MESSAGES[:download] - end - end - def user_can_download_code? authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code) end @@ -78,35 +58,6 @@ module Gitlab authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) end - def user_push_access_check(changes) - unless authentication_abilities.include?(:push_code) - raise UnauthorizedError, ERROR_MESSAGES[:upload] - end - - if changes.blank? - return # Allow access. - end - - unless project.repository.exists? - raise UnauthorizedError, ERROR_MESSAGES[:no_repo] - end - - changes_list = Gitlab::ChangesList.new(changes) - - # Iterate over all changes to find if user allowed all of them to be applied - changes_list.each do |change| - status = change_access_check(change) - unless status.allowed? - # If user does not have access to make at least one change - cancel all push - raise UnauthorizedError, status.message - end - end - end - - def change_access_check(change) - Checks::ChangeAccess.new(change, user_access: user_access, project: project, env: @env).exec - end - def protocol_allowed? Gitlab::ProtocolAccess.allowed?(protocol) end @@ -120,6 +71,8 @@ module Gitlab end def check_active_user! + return if deploy_key? + if user && !user_access.allowed? raise UnauthorizedError, "Your account has been blocked." end @@ -137,33 +90,92 @@ module Gitlab end end - def matching_merge_request?(newrev, branch_name) - Checks::MatchingMergeRequest.new(newrev, branch_name, project).match? + def check_repository_existence! + unless project.repository.exists? + raise UnauthorizedError, ERROR_MESSAGES[:no_repo] + end end - def deploy_key - actor if actor.is_a?(DeployKey) + def check_download_access! + return if deploy_key? + + passed = user_can_download_code? || + build_can_download_code? || + guest_can_download_code? + + unless passed + raise UnauthorizedError, ERROR_MESSAGES[:download] + end end - def deploy_key_can_read_project? + def check_push_access!(changes) if deploy_key - return true if project.public? - deploy_key.projects.include?(project) + check_deploy_key_push_access! + elsif user + check_user_push_access! else - false + raise UnauthorizedError, ERROR_MESSAGES[:upload] end + + return if changes.blank? # Allow access. + + check_change_access!(changes) end - def can_read_project? - if user - user_access.can_read_project? - elsif deploy_key - deploy_key_can_read_project? - else - Guest.can?(:read_project, project) + def check_user_push_access! + unless authentication_abilities.include?(:push_code) + raise UnauthorizedError, ERROR_MESSAGES[:upload] end end + def check_deploy_key_push_access! + unless deploy_key.can_push_to?(project) + raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload] + end + end + + def check_change_access!(changes) + changes_list = Gitlab::ChangesList.new(changes) + + # Iterate over all changes to find if user allowed all of them to be applied + changes_list.each do |change| + status = check_single_change_access(change) + unless status.allowed? + # If user does not have access to make at least one change - cancel all push + raise UnauthorizedError, status.message + end + end + end + + def check_single_change_access(change) + Checks::ChangeAccess.new( + change, + user_access: user_access, + project: project, + env: @env, + skip_authorization: deploy_key?).exec + end + + def matching_merge_request?(newrev, branch_name) + Checks::MatchingMergeRequest.new(newrev, branch_name, project).match? + end + + def deploy_key + actor if deploy_key? + end + + def deploy_key? + actor.is_a?(DeployKey) + end + + def can_read_project? + if deploy_key + deploy_key.has_access_to?(project) + elsif user + user.can?(:read_project, project) + end || Guest.can?(:read_project, project) + end + protected def user diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb index 2c06c4ff1ef..67eaa5e088d 100644 --- a/lib/gitlab/git_access_wiki.rb +++ b/lib/gitlab/git_access_wiki.rb @@ -1,6 +1,6 @@ module Gitlab class GitAccessWiki < GitAccess - def guest_can_downlod_code? + def guest_can_download_code? Guest.can?(:download_wiki_code, project) end @@ -8,7 +8,7 @@ module Gitlab authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_wiki_code) end - def change_access_check(change) + def check_single_change_access(change) if user_access.can_do_action?(:create_wiki) build_status_object(true) else diff --git a/lib/gitlab/template/gitlab_ci_yml_template.rb b/lib/gitlab/template/gitlab_ci_yml_template.rb index d19b0a52043..9d2ecee9756 100644 --- a/lib/gitlab/template/gitlab_ci_yml_template.rb +++ b/lib/gitlab/template/gitlab_ci_yml_template.rb @@ -15,7 +15,7 @@ module Gitlab { 'General' => '', 'Pages' => 'Pages', - 'Autodeploy' => 'autodeploy' + 'Auto deploy' => 'autodeploy' } end @@ -28,7 +28,7 @@ module Gitlab end def dropdown_names(context) - categories = context == 'autodeploy' ? ['Autodeploy'] : ['General', 'Pages'] + categories = context == 'autodeploy' ? ['Auto deploy'] : ['General', 'Pages'] super().slice(*categories) end end diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb index d4020af76f9..19ab76ae80f 100644 --- a/lib/gitlab/themes.rb +++ b/lib/gitlab/themes.rb @@ -15,7 +15,7 @@ module Gitlab Theme.new(1, 'Graphite', 'ui_graphite'), Theme.new(2, 'Charcoal', 'ui_charcoal'), Theme.new(3, 'Green', 'ui_green'), - Theme.new(4, 'Gray', 'ui_gray'), + Theme.new(4, 'Black', 'ui_black'), Theme.new(5, 'Violet', 'ui_violet'), Theme.new(6, 'Blue', 'ui_blue') ].freeze diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 9858d2e7d83..6c7e673fb9f 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -8,6 +8,8 @@ module Gitlab end def can_do_action?(action) + return false if no_user_or_blocked? + @permission_cache ||= {} @permission_cache[action] ||= user.can?(action, project) end @@ -17,7 +19,7 @@ module Gitlab end def allowed? - return false if user.blank? || user.blocked? + return false if no_user_or_blocked? if user.requires_ldap_check? && user.try_obtain_ldap_lease return false unless Gitlab::LDAP::Access.allowed?(user) @@ -27,7 +29,7 @@ module Gitlab end def can_push_to_branch?(ref) - return false unless user + return false if no_user_or_blocked? if project.protected_branch?(ref) return true if project.empty_repo? && project.user_can_push_to_empty_repo?(user) @@ -40,7 +42,7 @@ module Gitlab end def can_merge_to_branch?(ref) - return false unless user + return false if no_user_or_blocked? if project.protected_branch?(ref) access_levels = project.protected_branches.matching(ref).map(&:merge_access_levels).flatten @@ -51,9 +53,15 @@ module Gitlab end def can_read_project? - return false unless user + return false if no_user_or_blocked? user.can?(:read_project, project) end + + private + + def no_user_or_blocked? + user.nil? || user.blocked? + end end end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index dbdd4e977e8..a2eca74a3c8 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -63,8 +63,7 @@ namespace :gitlab do if project.persisted? puts " * Created #{project.name} (#{repo_path})".color(:green) - project.update_repository_size - project.update_commit_count + ProjectCacheWorker.perform(project.id) else puts " * Failed trying to create #{project.name} (#{repo_path})".color(:red) puts " Errors: #{project.errors.messages}".color(:red) diff --git a/lib/tasks/gitlab/update_commit_count.rake b/lib/tasks/gitlab/update_commit_count.rake deleted file mode 100644 index 3bd10b0208b..00000000000 --- a/lib/tasks/gitlab/update_commit_count.rake +++ /dev/null @@ -1,20 +0,0 @@ -namespace :gitlab do - desc "GitLab | Update commit count for projects" - task update_commit_count: :environment do - projects = Project.where(commit_count: 0) - puts "#{projects.size} projects need to be updated. This might take a while." - ask_to_continue unless ENV['force'] == 'yes' - - projects.find_each(batch_size: 100) do |project| - print "#{project.name_with_namespace.color(:yellow)} ... " - - unless project.repo_exists? - puts "skipping, because the repo is empty".color(:magenta) - next - end - - project.update_commit_count - puts project.commit_count.to_s.color(:green) - end - end -end diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb new file mode 100644 index 00000000000..288984cfba9 --- /dev/null +++ b/spec/controllers/dashboard/todos_controller_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Dashboard::TodosController do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:todo_service) { TodoService.new } + + describe 'GET #index' do + before do + sign_in(user) + project.team << [user, :developer] + end + + context 'when using pagination' do + let(:last_page) { user.todos.page().total_pages } + let!(:issues) { create_list(:issue, 2, project: project, assignee: user) } + + before do + issues.each { |issue| todo_service.new_issue(issue, user) } + allow(Kaminari.config).to receive(:default_per_page).and_return(1) + end + + it 'redirects to last_page if page number is larger than number of pages' do + get :index, page: (last_page + 1).to_param + + expect(response).to redirect_to(dashboard_todos_path(page: last_page)) + end + + it 'redirects to correspondent page' do + get :index, page: last_page + + expect(assigns(:todos).current_page).to eq(last_page) + expect(response).to have_http_status(200) + end + end + end +end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 3efef757ae2..f35c5d992d9 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' describe Projects::BlobController do - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } before do @@ -84,5 +84,35 @@ describe Projects::BlobController do end end end + + context 'when user has forked project' do + let(:guest) { create(:user) } + let!(:forked_project) { Projects::ForkService.new(project, guest).execute } + let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, source_branch: "fork-test-1", target_branch: "master") } + + before { sign_in(guest) } + + it "redirects to forked project new merge request" do + default_params[:target_branch] = "fork-test-1" + default_params[:create_merge_request] = 1 + + allow_any_instance_of(Files::UpdateService).to receive(:commit).and_return(:success) + + put :update, default_params + + expect(response).to redirect_to( + new_namespace_project_merge_request_path( + forked_project.namespace, + forked_project, + merge_request: { + source_project_id: forked_project.id, + target_project_id: project.id, + source_branch: "fork-test-1", + target_branch: "master" + } + ) + ) + end + end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index dbe5ddccbcf..e2321f2034b 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -52,6 +52,36 @@ describe Projects::IssuesController do expect(response).to have_http_status(404) end end + + context 'with page param' do + let(:last_page) { project.issues.page().total_pages } + let!(:issue_list) { create_list(:issue, 2, project: project) } + + before do + sign_in(user) + project.team << [user, :developer] + allow(Kaminari.config).to receive(:default_per_page).and_return(1) + end + + it 'redirects to last_page if page number is larger than number of pages' do + get :index, + namespace_id: project.namespace.path.to_param, + project_id: project.path.to_param, + page: (last_page + 1).to_param + + expect(response).to redirect_to(namespace_project_issues_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) + end + + it 'redirects to specified page' do + get :index, + namespace_id: project.namespace.path.to_param, + project_id: project.path.to_param, + page: last_page.to_param + + expect(assigns(:issues).current_page).to eq(last_page) + expect(response).to have_http_status(200) + end + end end describe 'GET #new' do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 440b897ddc6..2a411d78395 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -127,11 +127,29 @@ describe Projects::MergeRequestsController do end describe 'GET index' do - def get_merge_requests + def get_merge_requests(page = nil) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, - state: 'opened' + state: 'opened', page: page.to_param + end + + context 'when page param' do + let(:last_page) { project.merge_requests.page().total_pages } + let!(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } + + it 'redirects to last_page if page number is larger than number of pages' do + get_merge_requests(last_page + 1) + + expect(response).to redirect_to(namespace_project_merge_requests_path(page: last_page, state: controller.params[:state], scope: controller.params[:scope])) + end + + it 'redirects to specified page' do + get_merge_requests(last_page) + + expect(assigns(:merge_requests).current_page).to eq(last_page) + expect(response).to have_http_status(200) + end end context 'when filtering by opened state' do diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb index 72a3ebf2ebd..32b0e42c3cd 100644 --- a/spec/controllers/projects/snippets_controller_spec.rb +++ b/spec/controllers/projects/snippets_controller_spec.rb @@ -11,6 +11,28 @@ describe Projects::SnippetsController do end describe 'GET #index' do + context 'when page param' do + let(:last_page) { project.snippets.page().total_pages } + let!(:project_snippet) { create(:project_snippet, :public, project: project, author: user) } + + it 'redirects to last_page if page number is larger than number of pages' do + get :index, + namespace_id: project.namespace.path, + project_id: project.path, page: (last_page + 1).to_param + + expect(response).to redirect_to(namespace_project_snippets_path(page: last_page)) + end + + it 'redirects to specified page' do + get :index, + namespace_id: project.namespace.path, + project_id: project.path, page: last_page.to_param + + expect(assigns(:snippets).current_page).to eq(last_page) + expect(response).to have_http_status(200) + end + end + context 'when the project snippet is private' do let!(:project_snippet) { create(:project_snippet, :private, project: project, author: user) } diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb index a81645acd2b..477fab9e964 100644 --- a/spec/factories/lfs_objects.rb +++ b/spec/factories/lfs_objects.rb @@ -2,7 +2,7 @@ include ActionDispatch::TestProcess FactoryGirl.define do factory :lfs_object do - oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" + sequence(:oid) { |n| "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a%05x" % n } size 499013 end diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb new file mode 100644 index 00000000000..72d43096216 --- /dev/null +++ b/spec/factories/project_statistics.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :project_statistics do + project { create :project } + namespace { project.namespace } + end +end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index 0aa01fc499a..9c19db6b420 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -17,6 +17,16 @@ feature 'Admin Groups', feature: true do end end + describe 'show a group' do + scenario 'shows the group' do + group = create(:group, :private) + + visit admin_group_path(group) + + expect(page).to have_content("Group: #{group.name}") + end + end + describe 'group edit' do scenario 'shows the visibility level radio populated with the group visibility_level value' do group = create(:group, :private) diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index e31325ce47b..55ffc6761f8 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Admin::Users", feature: true do + include WaitForAjax + before { login_as :admin } describe "GET /admin/users" do @@ -252,5 +254,20 @@ describe "Admin::Users", feature: true do end expect(page).to have_content @project.name end + + it 'shows the group access level' do + within(:css, '.append-bottom-default + .panel') do + expect(page).to have_content 'Developer' + end + end + + it 'allows group membership to be revoked', js: true do + page.within(first('.group_member')) do + find('.btn-remove').click + end + wait_for_ajax + + expect(page).not_to have_selector('.group_member') + end end end diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index 92f1ab90881..ea7a97d1d4f 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -26,7 +26,7 @@ describe 'Auto deploy' do it 'does not show a button to set up auto deploy' do visit namespace_project_path(project.namespace, project) - expect(page).to have_no_content('Set up autodeploy') + expect(page).to have_no_content('Set up auto deploy') end end @@ -37,11 +37,11 @@ describe 'Auto deploy' do end it 'shows a button to set up auto deploy' do - expect(page).to have_link('Set up autodeploy') + expect(page).to have_link('Set up auto deploy') end - it 'includes Kubernetes as an available template', js: true do - click_link 'Set up autodeploy' + it 'includes OpenShift as an available template', js: true do + click_link 'Set up auto deploy' click_button 'Choose a GitLab CI Yaml template' within '.gitlab-ci-yml-selector' do @@ -49,8 +49,8 @@ describe 'Auto deploy' do end end - it 'creates a merge request using "autodeploy" branch', js: true do - click_link 'Set up autodeploy' + it 'creates a merge request using "auto-deploy" branch', js: true do + click_link 'Set up auto deploy' click_button 'Choose a GitLab CI Yaml template' within '.gitlab-ci-yml-selector' do click_on 'OpenShift' @@ -58,7 +58,7 @@ describe 'Auto deploy' do wait_for_ajax click_button 'Commit Changes' - expect(page).to have_content('New Merge Request From autodeploy into master') + expect(page).to have_content('New Merge Request From auto-deploy into master') end end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 973d5b286e9..bfac5a1b8ab 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -109,7 +109,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search backlog list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue1.title) end @@ -122,7 +122,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search done list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue8.title) end @@ -135,7 +135,7 @@ describe 'Issue Boards', feature: true, js: true do end it 'search list' do - page.within('#js-boards-seach') do + page.within('#js-boards-search') do find('.form-control').set(issue5.title) end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index 44dfc2dff45..dc9d09fa396 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -6,7 +6,7 @@ feature 'Tooltips on .timeago dates', feature: true, js: true do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:created_date) { Date.yesterday.to_time } - let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P UTC') } + let(:expected_format) { created_date.strftime('%b %-d, %Y %l:%M%P') } context 'on the activity tab' do before do diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb index 4b19886274e..a515c92db37 100644 --- a/spec/features/groups_spec.rb +++ b/spec/features/groups_spec.rb @@ -107,4 +107,17 @@ feature 'Group', feature: true do expect(page).to have_css('.group-home-desc a[rel]') end end + + describe 'group page with nested groups', js: true do + let!(:group) { create(:group) } + let!(:nested_group) { create(:group, parent: group) } + let!(:path) { group_path(group) } + + it 'has nested groups tab with nested groups inside' do + visit path + click_link 'Subgroups' + + expect(page).to have_content(nested_group.full_name) + end + end end diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb index 9a2b879e789..73553f97d6f 100644 --- a/spec/features/issuables/default_sort_order_spec.rb +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -180,16 +180,10 @@ describe 'Projects > Issuables > Default sort order', feature: true do end def visit_merge_requests_with_state(project, state) - visit_merge_requests project - visit_issuables_with_state state + visit_merge_requests project, state: state end def visit_issues_with_state(project, state) - visit_issues project - visit_issuables_with_state state - end - - def visit_issuables_with_state(state) - within('.issues-state-filters') { find("span", text: state.titleize).click } + visit_issues project, state: state end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index d0294908d2c..82c9bd0e6e6 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -47,7 +47,19 @@ feature 'GFM autocomplete', feature: true, js: true do expect_to_wrap(true, label_item, note, label.title) end - it "does not show drpdown when preceded with a special character" do + it "shows dropdown after a new line" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('test') + note.native.send_keys(:enter) + note.native.send_keys(:enter) + note.native.send_keys('@') + end + + expect(page).to have_selector('.atwho-container') + end + + it "does not show dropdown when preceded with a special character" do note = find('#note_note') page.within '.timeline-content-form' do note.native.send_keys('') @@ -65,6 +77,17 @@ feature 'GFM autocomplete', feature: true, js: true do expect(page).to have_selector('.atwho-container', visible: false) end + it "does not throw an error if no labels exist" do + note = find('#note_note') + page.within '.timeline-content-form' do + note.native.send_keys('') + note.native.send_keys('~') + note.click + end + + expect(page).to have_selector('.atwho-container', visible: false) + end + it 'doesn\'t wrap for assignee values' do note = find('#note_note') page.within '.timeline-content-form' do diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index 3f2da1c380c..31f75512f4a 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -30,7 +30,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/due 2016-08-28") expect(page).not_to have_content '/due 2016-08-28' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' issue.reload @@ -51,7 +51,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/due 2016-08-28") expect(page).to have_content '/due 2016-08-28' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' issue.reload @@ -70,7 +70,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/remove_due_date") expect(page).not_to have_content '/remove_due_date' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' issue.reload @@ -91,7 +91,7 @@ feature 'Issues > User uses slash commands', feature: true, js: true do write_note("/remove_due_date") expect(page).to have_content '/remove_due_date' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' issue.reload diff --git a/spec/features/merge_requests/closes_issues_spec.rb b/spec/features/merge_requests/closes_issues_spec.rb index dc32c8f7373..c73065cdce1 100644 --- a/spec/features/merge_requests/closes_issues_spec.rb +++ b/spec/features/merge_requests/closes_issues_spec.rb @@ -41,7 +41,7 @@ feature 'Merge Request closing issues message', feature: true do let(:merge_request_description) { "Description\n\nRefers to #{issue_1.to_reference} and #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not closed.") + expect(page).to have_content("Issues #{issue_1.to_reference} and #{issue_2.to_reference} are mentioned but will not be closed.") end end @@ -49,7 +49,7 @@ feature 'Merge Request closing issues message', feature: true do let(:merge_request_description) { "Description\n\ncloses #{issue_1.to_reference}\n\n refers to #{issue_2.to_reference}" } it 'does not display closing issue message' do - expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not closed.") + expect(page).to have_content("Accepting this merge request will close issue #{issue_1.to_reference}. Issue #{issue_2.to_reference} is mentioned but will not be closed.") end end end diff --git a/spec/features/merge_requests/merge_request_versions_spec.rb b/spec/features/merge_requests/merge_request_versions_spec.rb index 09451f41de4..cd2272dd38f 100644 --- a/spec/features/merge_requests/merge_request_versions_spec.rb +++ b/spec/features/merge_requests/merge_request_versions_spec.rb @@ -24,7 +24,7 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-dropdown' do find('.btn-default').click - click_link 'version 1' + find(:link, 'version 1').trigger('click') end end @@ -45,7 +45,7 @@ feature 'Merge Request versions', js: true, feature: true do before do page.within '.mr-version-compare-dropdown' do find('.btn-default').click - click_link 'version 1' + find(:link, 'version 1').trigger('click') end end diff --git a/spec/features/merge_requests/user_uses_slash_commands_spec.rb b/spec/features/merge_requests/user_uses_slash_commands_spec.rb index 7b8af555f0e..b1b3a47a1ce 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -31,7 +31,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq true end @@ -42,7 +42,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq false end @@ -61,7 +61,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do write_note("/wip") expect(page).not_to have_content '/wip' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' expect(merge_request.reload.work_in_progress?).to eq false end diff --git a/spec/features/milestones/milestones_spec.rb b/spec/features/milestones/milestones_spec.rb index 8b603f51545..aadd72a9f8e 100644 --- a/spec/features/milestones/milestones_spec.rb +++ b/spec/features/milestones/milestones_spec.rb @@ -1,6 +1,8 @@ require 'rails_helper' describe 'Milestone draggable', feature: true, js: true do + include WaitForAjax + let(:milestone) { create(:milestone, project: project, title: 8.14) } let(:project) { create(:empty_project, :public) } let(:user) { create(:user) } @@ -74,6 +76,8 @@ describe 'Milestone draggable', feature: true, js: true do visit namespace_project_milestone_path(project.namespace, project, milestone) issue.drag_to(issue_target) + + wait_for_ajax end def create_and_drag_merge_request(params = {}) @@ -82,5 +86,7 @@ describe 'Milestone draggable', feature: true, js: true do visit namespace_project_milestone_path(project.namespace, project, milestone) page.find("a[href='#tab-merge-requests']").click merge_request.drag_to(merge_request_target) + + wait_for_ajax end end diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index 3bb33394be7..9079350186d 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -42,6 +42,17 @@ describe 'Edit Project Settings', feature: true do end end + context "When external issue tracker is enabled" do + it "does not hide issues tab" do + project.project_feature.update(issues_access_level: ProjectFeature::DISABLED) + allow_any_instance_of(Project).to receive(:external_issue_tracker).and_return(JiraService.new) + + visit namespace_project_path(project.namespace, project) + + expect(page).to have_selector(".shortcuts-issues") + end + end + context "pipelines subtabs" do it "shows builds when enabled" do visit namespace_project_pipelines_path(project.namespace, project) diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb index 08a97085a9c..ca25c696f75 100644 --- a/spec/features/tags/master_creates_tag_spec.rb +++ b/spec/features/tags/master_creates_tag_spec.rb @@ -34,7 +34,7 @@ feature 'Master creates tag', feature: true do expect(current_path).to eq( namespace_project_tag_path(project.namespace, project, 'v3.0')) expect(page).to have_content 'v3.0' - page.within 'pre.body' do + page.within 'pre.wrap' do expect(page).to have_content "Awesome tag message\n\n- hello\n- world" end end diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index be21b403084..a8d00bb8e5a 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -45,12 +45,12 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: it 'allows registering a new device with a name' do visit profile_account_path manage_two_factor_authentication - expect(page.body).to match("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using mobile") u2f_device = register_u2f_device - expect(page.body).to match(u2f_device.name) - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content(u2f_device.name) + expect(page).to have_content('Your U2F device was registered') end it 'allows registering more than one device' do @@ -59,30 +59,30 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # First device manage_two_factor_authentication first_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') # Second device second_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') - expect(page.body).to match(first_device.name) - expect(page.body).to match(second_device.name) + expect(page).to have_content(first_device.name) + expect(page).to have_content(second_device.name) expect(U2fRegistration.count).to eq(2) end it 'allows deleting a device' do visit profile_account_path manage_two_factor_authentication - expect(page.body).to match("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using mobile") first_u2f_device = register_u2f_device second_u2f_device = register_u2f_device click_on "Delete", match: :first - expect(page.body).to match('Successfully deleted') + expect(page).to have_content('Successfully deleted') expect(page.body).not_to match(first_u2f_device.name) - expect(page.body).to match(second_u2f_device.name) + expect(page).to have_content(second_u2f_device.name) end end @@ -91,7 +91,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: visit profile_account_path manage_two_factor_authentication u2f_device = register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') logout # Second user @@ -100,7 +100,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: visit profile_account_path manage_two_factor_authentication register_u2f_device(u2f_device) - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') expect(U2fRegistration.count).to eq(2) end @@ -117,8 +117,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: click_on 'Register U2F Device' expect(U2fRegistration.count).to eq(0) - expect(page.body).to match("The form contains the following error") - expect(page.body).to match("did not send a valid JSON response") + expect(page).to have_content("The form contains the following error") + expect(page).to have_content("did not send a valid JSON response") end it "allows retrying registration" do @@ -130,12 +130,12 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: click_on 'Setup New U2F Device' expect(page).to have_content('Your device was successfully set up') click_on 'Register U2F Device' - expect(page.body).to match("The form contains the following error") + expect(page).to have_content("The form contains the following error") # Successful registration register_u2f_device - expect(page.body).to match('Your U2F device was registered') + expect(page).to have_content('Your U2F device was registered') expect(U2fRegistration.count).to eq(1) end end @@ -160,10 +160,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - expect(page.body).to match('href="/users/sign_out"') + + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_css('.sign-out-link', visible: false) end end @@ -173,11 +172,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - expect(page.body).to match('href="/users/sign_out"') + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_css('.sign-out-link', visible: false) end end @@ -185,8 +182,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: login_with(user, remember: true) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') + expect(page).to have_content('We heard back from your U2F device') within 'div#js-authenticate-u2f' do field = first('input#user_remember_me', visible: false) @@ -208,11 +204,8 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the old U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" - - expect(page.body).to match('Authentication via U2F device failed') + expect(page).to have_content('We heard back from your U2F device') + expect(page).to have_content('Authentication via U2F device failed') end end @@ -229,11 +222,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: # Try authenticating user with the same U2F device login_as(current_user) @u2f_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('href="/users/sign_out"') + expect(page).to have_css('.sign-out-link', visible: false) end end end @@ -243,11 +234,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: unregistered_device = FakeU2fDevice.new(page, FFaker::Name.first_name) login_as(user) unregistered_device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('Authentication via U2F device failed') + expect(page).to have_content('Authentication via U2F device failed') end end @@ -270,11 +259,9 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: [first_device, second_device].each do |device| login_as(user) device.respond_to_u2f_authentication - click_on "Sign in via U2F device" - expect(page.body).to match('We heard back from your U2F device') - click_on "Authenticate via U2F Device" + expect(page).to have_content('We heard back from your U2F device') - expect(page.body).to match('href="/users/sign_out"') + expect(page).to have_css('.sign-out-link', visible: false) logout end @@ -299,4 +286,50 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', feature: end end end + + describe 'fallback code authentication' do + let(:user) { create(:user) } + + def assert_fallback_ui(page) + expect(page).to have_button('Verify code') + expect(page).to have_css('#user_otp_attempt') + expect(page).not_to have_link('Sign in via 2FA code') + expect(page).not_to have_css('#js-authenticate-u2f') + end + + before do + # Register and logout + login_as(user) + user.update_attribute(:otp_required_for_login, true) + visit profile_account_path + end + + describe 'when no u2f device is registered' do + before do + logout + login_with(user) + end + + it 'shows the fallback otp code UI' do + assert_fallback_ui(page) + end + end + + describe 'when a u2f device is registered' do + before do + manage_two_factor_authentication + @u2f_device = register_u2f_device + logout + login_with(user) + end + + it 'provides a button that shows the fallback otp code UI' do + expect(page).to have_link('Sign in via 2FA code') + + click_link('Sign in via 2FA code') + + assert_fallback_ui(page) + end + end + end end diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb new file mode 100644 index 00000000000..4627a1e1872 --- /dev/null +++ b/spec/helpers/storage_helper_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe StorageHelper do + describe '#storage_counter' do + it 'formats bytes to one decimal place' do + expect(helper.storage_counter(1.23.megabytes)).to eq '1.2 MB' + end + + it 'does not add decimals for sizes < 1 MB' do + expect(helper.storage_counter(23.5.kilobytes)).to eq '24 KB' + end + + it 'does not add decimals for zeroes' do + expect(helper.storage_counter(2.megabytes)).to eq '2 MB' + end + + it 'uses commas as thousands separator' do + expect(helper.storage_counter(100_000_000_000_000_000)).to eq '90,949.5 TB' + end + end +end diff --git a/spec/javascripts/fixtures/project_title.html.haml b/spec/javascripts/fixtures/project_title.html.haml index 4547feeb212..9d1f7877116 100644 --- a/spec/javascripts/fixtures/project_title.html.haml +++ b/spec/javascripts/fixtures/project_title.html.haml @@ -4,7 +4,7 @@ GitLab Org %a.project-item-select-holder{href: "/gitlab-org/gitlab-test"} GitLab Test - %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content" } + %i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle{ "data-toggle" => "dropdown", "data-target" => ".header-content", "data-order-by" => "last_activity_at" } .js-dropdown-menu-projects .dropdown-menu.dropdown-select.dropdown-menu-projects .dropdown-title diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js index bb802a4b5e3..216b77f37c0 100644 --- a/spec/javascripts/project_title_spec.js +++ b/spec/javascripts/project_title_spec.js @@ -26,6 +26,7 @@ var fakeAjaxResponse = function fakeAjaxResponse(req) { var d; expect(req.url).toBe('/api/v3/projects.json?simple=true'); + expect(req.data).toEqual({ search: '', order_by: 'last_activity_at', per_page: 20 }); d = $.Deferred(); d.resolve(this.projects_data); return d.promise(); diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index a8874ab12d3..064d18519ea 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -14,18 +14,19 @@ beforeEach(function() { this.u2fDevice = new MockU2FDevice; this.container = $("#js-authenticate-u2f"); - this.component = new U2FAuthenticate(this.container, { - sign_requests: [] - }, "token"); + this.component = new window.gl.U2FAuthenticate( + this.container, + '#js-login-u2f-form', + { + sign_requests: [] + }, + document.querySelector('#js-login-2fa-device'), + document.querySelector('.js-2fa-form') + ); return this.component.start(); }); it('allows authenticating via a U2F device', function() { - var authenticatedMessage, deviceResponse, inProgressMessage, setupButton, setupMessage; - setupButton = this.container.find("#js-login-u2f-device"); - setupMessage = this.container.find("p"); - expect(setupMessage.text()).toContain('Insert your security key'); - expect(setupButton.text()).toBe('Sign in via U2F device'); - setupButton.trigger('click'); + var authenticatedMessage, deviceResponse, inProgressMessage; inProgressMessage = this.container.find("p"); expect(inProgressMessage.text()).toContain("Trying to communicate with your device"); this.u2fDevice.respondToAuthenticateRequest({ @@ -33,7 +34,7 @@ }); authenticatedMessage = this.container.find("p"); deviceResponse = this.container.find('#js-device-response'); - expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server"); + expect(authenticatedMessage.text()).toContain('We heard back from your U2F device. You have been authenticated.'); return expect(deviceResponse.val()).toBe('{"deviceData":"this is data from the device"}'); }); return describe("errors", function() { @@ -62,7 +63,7 @@ deviceData: "this is data from the device" }); authenticatedMessage = this.container.find("p"); - return expect(authenticatedMessage.text()).toContain("Click this button to authenticate with the GitLab server"); + return expect(authenticatedMessage.text()).toContain("We heard back from your U2F device. You have been authenticated."); }); }); }); diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb index 50a5d1a19ba..0af36776a54 100644 --- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb @@ -12,7 +12,17 @@ describe Banzai::ReferenceParser::ExternalIssueParser, lib: true do context 'when the link has a data-issue attribute' do before { link['data-external-issue'] = 123 } - it_behaves_like "referenced feature visibility", "issues" + levels = [ProjectFeature::DISABLED, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] + + levels.each do |level| + it "creates reference when the feature is #{level}" do + project.project_feature.update(issues_access_level: level) + + visible_nodes = subject.nodes_visible_to_user(user, [link]) + + expect(visible_nodes).to include(link) + end + end end end diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb index 2a680f03476..f2bc15d39d7 100644 --- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb +++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb @@ -1,21 +1,30 @@ require 'spec_helper' describe Gitlab::Diff::FileCollection::MergeRequestDiff do - let(:merge_request) { create :merge_request } + let(:merge_request) { create(:merge_request) } + let(:diff_files) { described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files } - it 'does not hightlight binary files' do + it 'does not highlight binary files' do allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => false)) expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) - described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files + diff_files end - it 'does not hightlight file if blob is not accessable' do + it 'does not highlight file if blob is not accessable' do allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(nil) expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) - described_class.new(merge_request.merge_request_diff, diff_options: nil).diff_files + diff_files + end + + it 'does not files marked as undiffable in .gitattributes' do + allow_any_instance_of(Repository).to receive(:diffable?).and_return(false) + + expect_any_instance_of(Gitlab::Diff::File).not_to receive(:highlighted_diff_lines) + + diff_files end end diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb index c7a0139d32a..28698e89c33 100644 --- a/spec/lib/gitlab/email/reply_parser_spec.rb +++ b/spec/lib/gitlab/email/reply_parser_spec.rb @@ -88,8 +88,6 @@ describe Gitlab::Email::ReplyParser, lib: true do expect(test_parse_body(fixture_file("emails/inline_reply.eml"))). to eq( <<-BODY.strip_heredoc.chomp - On Wed, Oct 8, 2014 at 11:12 AM, techAPJ <info@unconfigured.discourse.org> wrote: - > techAPJ <https://meta.discourse.org/users/techapj> > November 28 > diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index f1d0a190002..44b84afde47 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -50,7 +50,7 @@ describe Gitlab::GitAccess, lib: true do end end - describe 'download_access_check' do + describe '#check_download_access!' do subject { access.check('git-upload-pack', '_any') } describe 'master permissions' do @@ -82,7 +82,7 @@ describe Gitlab::GitAccess, lib: true do end end - describe 'without acccess to project' do + describe 'without access to project' do context 'pull code' do it { expect(subject.allowed?).to be_falsey } end @@ -112,7 +112,7 @@ describe Gitlab::GitAccess, lib: true do end describe 'deploy key permissions' do - let(:key) { create(:deploy_key) } + let(:key) { create(:deploy_key, user: user) } let(:actor) { key } context 'pull code' do @@ -136,7 +136,7 @@ describe Gitlab::GitAccess, lib: true do end context 'from private project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:project, :private) } it { expect(subject).not_to be_allowed } end @@ -183,7 +183,7 @@ describe Gitlab::GitAccess, lib: true do end end - describe 'push_access_check' do + describe '#check_push_access!' do before { merge_into_protected_branch } let(:unprotected_branch) { FFaker::Internet.user_name } @@ -231,7 +231,7 @@ describe Gitlab::GitAccess, lib: true do permissions_matrix[role].each do |action, allowed| context action do - subject { access.push_access_check(changes[action]) } + subject { access.send(:check_push_access!, changes[action]) } it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } end end @@ -353,13 +353,13 @@ describe Gitlab::GitAccess, lib: true do end end - shared_examples 'can not push code' do + shared_examples 'pushing code' do |can| subject { access.check('git-receive-pack', '_any') } context 'when project is authorized' do before { authorize } - it { expect(subject).not_to be_allowed } + it { expect(subject).public_send(can, be_allowed) } end context 'when unauthorized' do @@ -386,7 +386,7 @@ describe Gitlab::GitAccess, lib: true do describe 'build authentication abilities' do let(:authentication_abilities) { build_authentication_abilities } - it_behaves_like 'can not push code' do + it_behaves_like 'pushing code', :not_to do def authorize project.team << [user, :reporter] end @@ -394,12 +394,26 @@ describe Gitlab::GitAccess, lib: true do end describe 'deploy key permissions' do - let(:key) { create(:deploy_key) } + let(:key) { create(:deploy_key, user: user, can_push: can_push) } let(:actor) { key } - it_behaves_like 'can not push code' do - def authorize - key.projects << project + context 'when deploy_key can push' do + let(:can_push) { true } + + it_behaves_like 'pushing code', :to do + def authorize + key.projects << project + end + end + end + + context 'when deploy_key cannot push' do + let(:can_push) { false } + + it_behaves_like 'pushing code', :not_to do + def authorize + key.projects << project + end end end end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb index 578db51631e..a5d172233cc 100644 --- a/spec/lib/gitlab/git_access_wiki_spec.rb +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -27,7 +27,7 @@ describe Gitlab::GitAccessWiki, lib: true do ['6f6d7e7ed 570e7b2ab refs/heads/master'] end - describe '#download_access_check' do + describe '#access_check_download!' do subject { access.check('git-upload-pack', '_any') } before do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index f420d71dee2..ceed9c942c1 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -192,6 +192,7 @@ project: - authorized_users - project_authorizations - route +- statistics award_emoji: - awardable - user diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 78d6b2c5032..ac26c831fd0 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -247,6 +247,7 @@ DeployKey: - type - fingerprint - public +- can_push Service: - id - type diff --git a/spec/migrations/remove_dot_git_from_usernames.rb b/spec/migrations/remove_dot_git_from_usernames.rb new file mode 100644 index 00000000000..1b1d2adc463 --- /dev/null +++ b/spec/migrations/remove_dot_git_from_usernames.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb') + +describe RemoveDotGitFromUsernames do + let(:user) { create(:user) } + + describe '#up' do + let(:migration) { described_class.new } + + before do + namespace = user.namespace + namespace.path = 'test.git' + namespace.save!(validate: false) + + user.username = 'test.git' + user.save!(validate: false) + end + + it 'renames user with .git in username' do + migration.up + + expect(user.reload.username).to eq('test_git') + expect(user.namespace.reload.path).to eq('test_git') + expect(user.namespace.route.path).to eq('test_git') + end + end +end diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb new file mode 100644 index 00000000000..4fb7ed36884 --- /dev/null +++ b/spec/migrations/rename_reserved_project_names_spec.rb @@ -0,0 +1,47 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_project_names.rb') + +# This migration uses multiple threads, and thus different transactions. This +# means data created in this spec may not be visible to some threads. To work +# around this we use the TRUNCATE cleaning strategy. +describe RenameReservedProjectNames, truncate: true do + let(:migration) { described_class.new } + let!(:project) { create(:empty_project) } + + before do + project.path = 'projects' + project.save!(validate: false) + end + + describe '#up' do + context 'when project repository exists' do + before { project.create_repository } + + context 'when no exception is raised' do + it 'renames project with reserved names' do + migration.up + + expect(project.reload.path).to eq('projects0') + end + end + + context 'when exception is raised during rename' do + before do + allow(project).to receive(:rename_repo).and_raise(StandardError) + end + + it 'captures exception from project rename' do + expect { migration.up }.not_to raise_error + end + end + end + + context 'when project repository does not exist' do + it 'does not raise error' do + expect { migration.up }.not_to raise_error + end + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index a7e90c8a381..7e1d1126b97 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -85,4 +85,30 @@ describe Ci::Build, models: true do it { expect(build.trace_file_path).to eq(build.old_path_to_trace) } end end + + describe '#update_project_statistics' do + let!(:build) { create(:ci_build, artifacts_size: 23) } + + it 'updates project statistics when the artifact size changes' do + expect(ProjectCacheWorker).to receive(:perform_async) + .with(build.project_id, [], [:build_artifacts_size]) + + build.artifacts_size = 42 + build.save! + end + + it 'does not update project statistics when the artifact size stays the same' do + expect(ProjectCacheWorker).not_to receive(:perform_async) + + build.name = 'changed' + build.save! + end + + it 'updates project statistics when the build is destroyed' do + expect(ProjectCacheWorker).to receive(:perform_async) + .with(build.project_id, [], [:build_artifacts_size]) + + build.destroy + end + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 79341d43c08..d1aee27057a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -464,6 +464,19 @@ describe Ci::Pipeline, models: true do end end + describe '.latest_successful_for' do + include_context 'with some outdated pipelines' + + let!(:latest_successful_pipeline) do + create_pipeline(:success, 'ref', 'D') + end + + it 'returns the latest successful pipeline' do + expect(described_class.latest_successful_for('ref')). + to eq(latest_successful_pipeline) + end + end + describe '#status' do let!(:build) { create(:ci_build, :created, pipeline: pipeline, name: 'test') } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 4fa06a8c60a..1078c959419 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -44,6 +44,45 @@ describe Issue, "Issuable" do it { expect(described_class).to respond_to(:assigned) } end + describe "before_save" do + describe "#update_cache_counts" do + context "when previous assignee exists" do + before do + assignee = create(:user) + issue.project.team << [assignee, :developer] + issue.update(assignee: assignee) + end + + it "updates cache counts for new assignee" do + user = create(:user) + + expect(user).to receive(:update_cache_counts) + + issue.update(assignee: user) + end + + it "updates cache counts for previous assignee" do + old_assignee = issue.assignee + allow(User).to receive(:find_by_id).with(old_assignee.id).and_return(old_assignee) + + expect(old_assignee).to receive(:update_cache_counts) + + issue.update(assignee: nil) + end + end + + context "when previous assignee does not exist" do + before{ issue.update(assignee: nil) } + + it "updates cache count for the new assignee" do + expect_any_instance_of(User).to receive(:update_cache_counts) + + issue.update(assignee: user) + end + end + end + end + describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 93623e8e99b..8ef8218cf74 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,8 +1,22 @@ require 'spec_helper' describe DeployKey, models: true do + include EmailHelpers + describe "Associations" do it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:projects) } end + + describe 'notification' do + let(:user) { create(:user) } + + it 'does not send a notification' do + perform_enqueued_jobs do + create(:deploy_key, user: user) + end + + should_not_email(user) + end + end end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 7d5ecfbaa64..45fe927202b 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -277,4 +277,15 @@ describe Group, models: true do it { is_expected.to be_valid } it { expect(subject.parent).to be_kind_of(Group) } end + + describe '#members_with_parents' do + let!(:group) { create(:group, :nested) } + let!(:master) { group.parent.add_user(create(:user), GroupMember::MASTER) } + let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) } + + it 'returns parents members' do + expect(group.members_with_parents).to include(developer) + expect(group.members_with_parents).to include(master) + end + end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 2a33d819138..7758b7ffa97 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Key, models: true do + include EmailHelpers + describe "Associations" do it { is_expected.to belong_to(:user) } end @@ -96,4 +98,16 @@ describe Key, models: true do expect(described_class.new(key: " #{valid_key} ").key).to eq(valid_key) end end + + describe 'notification' do + let(:user) { create(:user) } + + it 'sends a notification' do + perform_enqueued_jobs do + create(:key, user: user) + end + + should_email(user) + end + end end diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb new file mode 100644 index 00000000000..7bc278e350f --- /dev/null +++ b/spec/models/lfs_objects_project_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe LfsObjectsProject, models: true do + subject { create(:lfs_objects_project, project: project) } + let(:project) { create(:empty_project) } + + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:lfs_object) } + end + + describe 'validation' do + it { is_expected.to validate_presence_of(:lfs_object_id) } + it { is_expected.to validate_uniqueness_of(:lfs_object_id).scoped_to(:project_id).with_message("already exists in project") } + + it { is_expected.to validate_presence_of(:project_id) } + end + + describe '#update_project_statistics' do + it 'updates project statistics when the object is added' do + expect(ProjectCacheWorker).to receive(:perform_async) + .with(project.id, [], [:lfs_objects_size]) + + subject.save! + end + + it 'updates project statistics when the object is removed' do + subject.save! + + expect(ProjectCacheWorker).to receive(:perform_async) + .with(project.id, [], [:lfs_objects_size]) + + subject.destroy + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 5da00a8636a..646e6c6dbb3 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -283,12 +283,16 @@ describe MergeRequest, models: true do end describe '#issues_mentioned_but_not_closing' do - it 'detects issues mentioned in description but not closed' do - mentioned_issue = create(:issue, project: subject.project) + let(:closing_issue) { create :issue, project: subject.project } + let(:mentioned_issue) { create :issue, project: subject.project } + + let(:commit) { double('commit', safe_message: "Fixes #{closing_issue.to_reference}") } + it 'detects issues mentioned in description but not closed' do subject.project.team << [subject.author, :developer] - subject.description = "Is related to #{mentioned_issue.to_reference}" + subject.description = "Is related to #{mentioned_issue.to_reference} and #{closing_issue.to_reference}" + allow(subject).to receive(:commits).and_return([commit]) allow(subject.project).to receive(:default_branch). and_return(subject.target_branch) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 9fd06bb6b23..600538ff5f4 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -4,6 +4,7 @@ describe Namespace, models: true do let!(:namespace) { create(:namespace) } it { is_expected.to have_many :projects } + it { is_expected.to have_many :project_statistics } it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_id) } @@ -57,6 +58,50 @@ describe Namespace, models: true do end end + describe '.with_statistics' do + let(:namespace) { create :namespace } + + let(:project1) do + create(:empty_project, + namespace: namespace, + statistics: build(:project_statistics, + storage_size: 606, + repository_size: 101, + lfs_objects_size: 202, + build_artifacts_size: 303)) + end + + let(:project2) do + create(:empty_project, + namespace: namespace, + statistics: build(:project_statistics, + storage_size: 60, + repository_size: 10, + lfs_objects_size: 20, + build_artifacts_size: 30)) + end + + it "sums all project storage counters in the namespace" do + project1 + project2 + statistics = Namespace.with_statistics.find(namespace.id) + + expect(statistics.storage_size).to eq 666 + expect(statistics.repository_size).to eq 111 + expect(statistics.lfs_objects_size).to eq 222 + expect(statistics.build_artifacts_size).to eq 333 + end + + it "correctly handles namespaces without projects" do + statistics = Namespace.with_statistics.find(namespace.id) + + expect(statistics.storage_size).to eq 0 + expect(statistics.repository_size).to eq 0 + expect(statistics.lfs_objects_size).to eq 0 + expect(statistics.build_artifacts_size).to eq 0 + end + end + describe '#move_dir' do before do @namespace = create :namespace diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 88d5d14f855..fb225eb7625 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -49,6 +49,7 @@ describe Project, models: true do it { is_expected.to have_one(:gitlab_issue_tracker_service).dependent(:destroy) } it { is_expected.to have_one(:external_wiki_service).dependent(:destroy) } it { is_expected.to have_one(:project_feature).dependent(:destroy) } + it { is_expected.to have_one(:statistics).class_name('ProjectStatistics').dependent(:delete) } it { is_expected.to have_one(:import_data).class_name('ProjectImportData').dependent(:destroy) } it { is_expected.to have_one(:last_event).class_name('Event') } it { is_expected.to have_one(:forked_from_project).through(:forked_project_link) } @@ -1729,6 +1730,26 @@ describe Project, models: true do end end + describe '#update_project_statistics' do + let(:project) { create(:empty_project) } + + it "is called after creation" do + expect(project.statistics).to be_a ProjectStatistics + expect(project.statistics).to be_persisted + end + + it "copies the namespace_id" do + expect(project.statistics.namespace_id).to eq project.namespace_id + end + + it "updates the namespace_id when changed" do + namespace = create(:namespace) + project.update(namespace: namespace) + + expect(project.statistics.namespace_id).to eq namespace.id + end + end + def enable_lfs allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb new file mode 100644 index 00000000000..77403cc9eb0 --- /dev/null +++ b/spec/models/project_statistics_spec.rb @@ -0,0 +1,160 @@ +require 'rails_helper' + +describe ProjectStatistics, models: true do + let(:project) { create :empty_project } + let(:statistics) { project.statistics } + + describe 'constants' do + describe 'STORAGE_COLUMNS' do + it 'is an array of symbols' do + expect(described_class::STORAGE_COLUMNS).to be_kind_of Array + expect(described_class::STORAGE_COLUMNS.map(&:class).uniq).to eq [Symbol] + end + end + + describe 'STATISTICS_COLUMNS' do + it 'is an array of symbols' do + expect(described_class::STATISTICS_COLUMNS).to be_kind_of Array + expect(described_class::STATISTICS_COLUMNS.map(&:class).uniq).to eq [Symbol] + end + + it 'includes all storage columns' do + expect(described_class::STATISTICS_COLUMNS & described_class::STORAGE_COLUMNS).to eq described_class::STORAGE_COLUMNS + end + end + end + + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:namespace) } + end + + describe 'statistics columns' do + it "support values up to 8 exabytes" do + statistics.update!( + commit_count: 8.exabytes - 1, + repository_size: 2.exabytes, + lfs_objects_size: 2.exabytes, + build_artifacts_size: 4.exabytes - 1, + ) + + statistics.reload + + expect(statistics.commit_count).to eq(8.exabytes - 1) + expect(statistics.repository_size).to eq(2.exabytes) + expect(statistics.lfs_objects_size).to eq(2.exabytes) + expect(statistics.build_artifacts_size).to eq(4.exabytes - 1) + expect(statistics.storage_size).to eq(8.exabytes - 1) + end + end + + describe '#total_repository_size' do + it "sums repository and LFS object size" do + statistics.repository_size = 2 + statistics.lfs_objects_size = 3 + statistics.build_artifacts_size = 4 + + expect(statistics.total_repository_size).to eq 5 + end + end + + describe '#refresh!' do + before do + allow(statistics).to receive(:update_commit_count) + allow(statistics).to receive(:update_repository_size) + allow(statistics).to receive(:update_lfs_objects_size) + allow(statistics).to receive(:update_build_artifacts_size) + allow(statistics).to receive(:update_storage_size) + end + + context "without arguments" do + before do + statistics.refresh! + end + + it "sums all counters" do + expect(statistics).to have_received(:update_commit_count) + expect(statistics).to have_received(:update_repository_size) + expect(statistics).to have_received(:update_lfs_objects_size) + expect(statistics).to have_received(:update_build_artifacts_size) + end + end + + context "when passing an only: argument" do + before do + statistics.refresh! only: [:lfs_objects_size] + end + + it "only updates the given columns" do + expect(statistics).to have_received(:update_lfs_objects_size) + expect(statistics).not_to have_received(:update_commit_count) + expect(statistics).not_to have_received(:update_repository_size) + expect(statistics).not_to have_received(:update_build_artifacts_size) + end + end + end + + describe '#update_commit_count' do + before do + allow(project.repository).to receive(:commit_count).and_return(23) + statistics.update_commit_count + end + + it "stores the number of commits in the repository" do + expect(statistics.commit_count).to eq 23 + end + end + + describe '#update_repository_size' do + before do + allow(project.repository).to receive(:size).and_return(12.megabytes) + statistics.update_repository_size + end + + it "stores the size of the repository" do + expect(statistics.repository_size).to eq 12.megabytes + end + end + + describe '#update_lfs_objects_size' do + let!(:lfs_object1) { create(:lfs_object, size: 23.megabytes) } + let!(:lfs_object2) { create(:lfs_object, size: 34.megabytes) } + let!(:lfs_objects_project1) { create(:lfs_objects_project, project: project, lfs_object: lfs_object1) } + let!(:lfs_objects_project2) { create(:lfs_objects_project, project: project, lfs_object: lfs_object2) } + + before do + statistics.update_lfs_objects_size + end + + it "stores the size of related LFS objects" do + expect(statistics.lfs_objects_size).to eq 57.megabytes + end + end + + describe '#update_build_artifacts_size' do + let!(:pipeline) { create(:ci_pipeline, project: project) } + let!(:build1) { create(:ci_build, pipeline: pipeline, artifacts_size: 45.megabytes) } + let!(:build2) { create(:ci_build, pipeline: pipeline, artifacts_size: 56.megabytes) } + + before do + statistics.update_build_artifacts_size + end + + it "stores the size of related build artifacts" do + expect(statistics.build_artifacts_size).to eq 101.megabytes + end + end + + describe '#update_storage_size' do + it "sums all storage counters" do + statistics.update!( + repository_size: 2, + lfs_objects_size: 3, + ) + + statistics.reload + + expect(statistics.storage_size).to eq 5 + end + end +end diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb index a20ac303a53..5c34ff04152 100644 --- a/spec/policies/group_policy_spec.rb +++ b/spec/policies/group_policy_spec.rb @@ -105,4 +105,70 @@ describe GroupPolicy, models: true do is_expected.to include(*owner_permissions) end end + + describe 'private nested group inherit permissions' do + let(:nested_group) { create(:group, :private, parent: group) } + + subject { described_class.abilities(current_user, nested_group).to_set } + + context 'with no user' do + let(:current_user) { nil } + + it do + is_expected.not_to include(:read_group) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'guests' do + let(:current_user) { guest } + + it do + is_expected.to include(:read_group) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'reporter' do + let(:current_user) { reporter } + + it do + is_expected.to include(:read_group) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'developer' do + let(:current_user) { developer } + + it do + is_expected.to include(:read_group) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'master' do + let(:current_user) { master } + + it do + is_expected.to include(:read_group) + is_expected.to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'owner' do + let(:current_user) { owner } + + it do + is_expected.to include(:read_group) + is_expected.to include(*master_permissions) + is_expected.to include(*owner_permissions) + end + end + end end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index cdeb965b413..0e8d6faea27 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -35,6 +35,14 @@ describe API::Groups, api: true do expect(json_response.length).to eq(1) expect(json_response.first['name']).to eq(group1.name) end + + it "does not include statistics" do + get api("/groups", user1), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include 'statistics' + end end context "when authenticated as admin" do @@ -44,6 +52,31 @@ describe API::Groups, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(2) end + + it "does not include statistics by default" do + get api("/groups", admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + attributes = { + storage_size: 702, + repository_size: 123, + lfs_objects_size: 234, + build_artifacts_size: 345, + } + + project1.statistics.update!(attributes) + + get api("/groups", admin), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['statistics']).to eq attributes.stringify_keys + end end context "when using skip_groups in request" do diff --git a/spec/requests/api/helpers_spec.rb b/spec/requests/api/helpers_spec.rb index c3d7ac3eef8..b8ee2293a33 100644 --- a/spec/requests/api/helpers_spec.rb +++ b/spec/requests/api/helpers_spec.rb @@ -396,7 +396,7 @@ describe API::Helpers, api: true do %w[HEAD GET].each do |method_name| context "method is #{method_name}" do before do - expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name)) end it 'does not raise an error' do @@ -410,7 +410,7 @@ describe API::Helpers, api: true do %w[POST PUT PATCH DELETE].each do |method_name| context "method is #{method_name}" do before do - expect_any_instance_of(self.class).to receive(:route).and_return(double(route_method: method_name)) + expect_any_instance_of(self.class).to receive(:route).and_return(double(request_method: method_name)) end it 'calls authenticate!' do diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 8304c408064..f5788d15f93 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -49,7 +49,7 @@ describe API::Projects, api: true do end end - context 'when authenticated' do + context 'when authenticated as regular user' do it 'returns an array of projects' do get api('/projects', user) expect(response).to have_http_status(200) @@ -172,6 +172,22 @@ describe API::Projects, api: true do end end end + + it "does not include statistics by default" do + get api('/projects/all', admin) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + get api('/projects/all', admin), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).to include 'statistics' + end end end @@ -196,6 +212,32 @@ describe API::Projects, api: true do expect(json_response.first['name']).to eq(project4.name) expect(json_response.first['owner']['username']).to eq(user4.username) end + + it "does not include statistics by default" do + get api('/projects/owned', user4) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first).not_to include('statistics') + end + + it "includes statistics if requested" do + attributes = { + commit_count: 23, + storage_size: 702, + repository_size: 123, + lfs_objects_size: 234, + build_artifacts_size: 345, + } + + project4.statistics.update!(attributes) + + get api('/projects/owned', user4), statistics: true + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['statistics']).to eq attributes.stringify_keys + end end end @@ -630,6 +672,18 @@ describe API::Projects, api: true do expect(json_response['name']).to eq(project.name) end + it 'exposes namespace fields' do + get api("/projects/#{project.id}", user) + + expect(response).to have_http_status(200) + expect(json_response['namespace']).to eq({ + 'id' => user.namespace.id, + 'name' => user.namespace.name, + 'path' => user.namespace.path, + 'kind' => user.namespace.kind, + }) + end + describe 'permissions' do context 'all projects' do before { project.team << [user, :master] } diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 9a8d633d657..ad9d8a25af4 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -44,8 +44,7 @@ describe API::Settings, 'Settings', api: true do put api("/application/settings", admin), koding_enabled: true expect(response).to have_http_status(400) - expect(json_response['message']).to have_key('koding_url') - expect(json_response['message']['koding_url']).to include "can't be blank" + expect(json_response['error']).to eq('koding_url is missing') end end end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index d71bb08c218..5abda28e26f 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -371,12 +371,26 @@ describe 'Git HTTP requests', lib: true do shared_examples 'can download code only' do it 'downloads get status 200' do - clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token + allow_any_instance_of(Repository). + to receive(:exists?).and_return(true) + + clone_get "#{project.path_with_namespace}.git", + user: 'gitlab-ci-token', password: build.token expect(response).to have_http_status(200) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) end + it 'downloads from non-existing repository and gets 403' do + allow_any_instance_of(Repository). + to receive(:exists?).and_return(false) + + clone_get "#{project.path_with_namespace}.git", + user: 'gitlab-ci-token', password: build.token + + expect(response).to have_http_status(403) + end + it 'uploads get status 403' do push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 661b671301e..99c44bde151 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -122,12 +122,18 @@ describe Admin::HealthCheckController, "routing" do end describe Admin::GroupsController, "routing" do + let(:name) { 'complex.group-namegit' } + it "to #index" do expect(get("/admin/groups")).to route_to('admin/groups#index') end it "to #show" do - expect(get("/admin/groups/gitlab")).to route_to('admin/groups#show', id: 'gitlab') - expect(get("/admin/groups/gitlab/subgroup")).to route_to('admin/groups#show', id: 'gitlab/subgroup') + expect(get("/admin/groups/#{name}")).to route_to('admin/groups#show', id: name) + expect(get("/admin/groups/#{name}/subgroup")).to route_to('admin/groups#show', id: "#{name}/subgroup") + end + + it "to #edit" do + expect(get("/admin/groups/#{name}/edit")).to route_to('admin/groups#edit', id: name) end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 3303e808a9c..2a0f00ce937 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -583,7 +583,7 @@ describe GitPushService, services: true do service.push_commits = [commit] expect(ProjectCacheWorker).to receive(:perform_async). - with(project.id, %i(readme)) + with(project.id, %i(readme), %i(commit_count repository_size)) service.update_caches end @@ -596,7 +596,7 @@ describe GitPushService, services: true do it 'does not flush any conditional caches' do expect(ProjectCacheWorker).to receive(:perform_async). - with(project.id, []). + with(project.id, [], %i(commit_count repository_size)). and_call_original service.update_caches diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb index 71a0b8e2a12..14717a7455d 100644 --- a/spec/services/groups/create_service_spec.rb +++ b/spec/services/groups/create_service_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' -describe Groups::CreateService, services: true do - let!(:user) { create(:user) } +describe Groups::CreateService, '#execute', services: true do + let!(:user) { create(:user) } let!(:group_params) { { path: "group_path", visibility_level: Gitlab::VisibilityLevel::PUBLIC } } - describe "execute" do - let!(:service) { described_class.new(user, group_params ) } + describe 'visibility level restrictions' do + let!(:service) { described_class.new(user, group_params) } + subject { service.execute } context "create groups without restricted visibility level" do @@ -14,7 +15,29 @@ describe Groups::CreateService, services: true do context "cannot create group with restricted visibility level" do before { allow_any_instance_of(ApplicationSetting).to receive(:restricted_visibility_levels).and_return([Gitlab::VisibilityLevel::PUBLIC]) } + it { is_expected.not_to be_persisted } end end + + describe 'creating subgroup' do + let!(:group) { create(:group) } + let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) } + + subject { service.execute } + + context 'as group owner' do + before { group.add_owner(user) } + + it { is_expected.to be_persisted } + end + + context 'as guest' do + it 'does not save group and returns an error' do + is_expected.not_to be_persisted + expect(subject.errors[:parent_id].first).to eq('manage access required to create subgroup') + expect(subject.parent_id).to be_nil + end + end + end end diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 5f3020b6525..0475f38fe5e 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -52,7 +52,10 @@ describe Issuable::BulkUpdateService, services: true do context 'when the new assignee ID is a valid user' do it 'succeeds' do - result = bulk_update(issue, assignee_id: create(:user).id) + new_assignee = create(:user) + project.team << [new_assignee, :developer] + + result = bulk_update(issue, assignee_id: new_assignee.id) expect(result[:success]).to be_truthy expect(result[:count]).to eq(1) @@ -60,15 +63,16 @@ describe Issuable::BulkUpdateService, services: true do it 'updates the assignee to the use ID passed' do assignee = create(:user) + project.team << [assignee, :developer] expect { bulk_update(issue, assignee_id: assignee.id) } .to change { issue.reload.assignee }.from(user).to(assignee) end end - context 'when the new assignee ID is -1' do - it 'unassigns the issues' do - expect { bulk_update(issue, assignee_id: -1) } + context "when the new assignee ID is #{IssuableFinder::NONE}" do + it "unassigns the issues" do + expect { bulk_update(issue, assignee_id: IssuableFinder::NONE) } .to change { issue.reload.assignee }.to(nil) end end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 8bde61ee336..ac3834c32ff 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -135,6 +135,8 @@ describe Issues::CreateService, services: true do end end + it_behaves_like 'issuable create service' + it_behaves_like 'new issuable record that supports slash commands' context 'for a merge request' do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index eafbea46905..d83b09fd32c 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -142,6 +142,17 @@ describe Issues::UpdateService, services: true do update_issue(confidential: true) end + + it 'does not update assignee_id with unauthorized users' do + project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_issue(confidential: true) + non_member = create(:user) + original_assignee = issue.assignee + + update_issue(assignee_id: non_member.id) + + expect(issue.reload.assignee_id).to eq(original_assignee.id) + end end context 'todos' do diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index b8142889075..673c0bd6c9c 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -84,6 +84,8 @@ describe MergeRequests::CreateService, services: true do end end + it_behaves_like 'issuable create service' + context 'while saving references to issues that the created merge request closes' do let(:first_issue) { create(:issue, project: project) } let(:second_issue) { create(:issue, project: project) } diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb index 05cdbe5287a..35804d41b46 100644 --- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -11,6 +11,7 @@ describe MergeRequests::MergeRequestDiffCacheService do expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:write).with(cache_key, anything) allow_any_instance_of(Gitlab::Diff::File).to receive(:blob).and_return(double("text?" => true)) + allow_any_instance_of(Repository).to receive(:diffable?).and_return(true) subject.execute(merge_request) end diff --git a/spec/services/notes/slash_commands_service_spec.rb b/spec/services/notes/slash_commands_service_spec.rb index d1099884a02..960b5cd5e6f 100644 --- a/spec/services/notes/slash_commands_service_spec.rb +++ b/spec/services/notes/slash_commands_service_spec.rb @@ -5,6 +5,8 @@ describe Notes::SlashCommandsService, services: true do let(:project) { create(:empty_project) } let(:master) { create(:user).tap { |u| project.team << [u, :master] } } let(:assignee) { create(:user) } + + before { project.team << [assignee, :master] } end shared_examples 'note on noteable that does not support slash commands' do diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb index 72c8f7cd8ec..1f6919151de 100644 --- a/spec/services/users/refresh_authorized_projects_service_spec.rb +++ b/spec/services/users/refresh_authorized_projects_service_spec.rb @@ -54,12 +54,37 @@ describe Users::RefreshAuthorizedProjectsService do end describe '#update_authorizations' do - it 'does nothing when there are no rows to add and remove' do - expect(user).not_to receive(:remove_project_authorizations) - expect(ProjectAuthorization).not_to receive(:insert_authorizations) - expect(user).not_to receive(:set_authorized_projects_column) + context 'when there are no rows to add and remove' do + it 'does not change authorizations' do + expect(user).not_to receive(:remove_project_authorizations) + expect(ProjectAuthorization).not_to receive(:insert_authorizations) - service.update_authorizations([], []) + service.update_authorizations([], []) + end + + context 'when the authorized projects column is not set' do + before do + user.update!(authorized_projects_populated: nil) + end + + it 'populates the authorized projects column' do + service.update_authorizations([], []) + + expect(user.authorized_projects_populated).to eq true + end + end + + context 'when the authorized projects column is set' do + before do + user.update!(authorized_projects_populated: true) + end + + it 'does nothing' do + expect(user).not_to receive(:set_authorized_projects_column) + + service.update_authorizations([], []) + end + end end it 'removes authorizations that should be removed' do @@ -84,7 +109,7 @@ describe Users::RefreshAuthorizedProjectsService do it 'populates the authorized projects column' do # make sure we start with a nil value no matter what the default in the # factory may be. - user.update(authorized_projects_populated: nil) + user.update!(authorized_projects_populated: nil) service.update_authorizations([], [[user.id, project.id, Gitlab::Access::MASTER]]) diff --git a/spec/support/fake_u2f_device.rb b/spec/support/fake_u2f_device.rb index 8c407b867fe..a7605cd483a 100644 --- a/spec/support/fake_u2f_device.rb +++ b/spec/support/fake_u2f_device.rb @@ -5,7 +5,7 @@ class FakeU2fDevice @page = page @name = name end - + def respond_to_u2f_registration app_id = @page.evaluate_script('gon.u2f.app_id') challenges = @page.evaluate_script('gon.u2f.challenges') @@ -28,6 +28,7 @@ class FakeU2fDevice u2f.sign = function(appId, challenges, signRequests, callback) { callback(#{json_response}); }; + window.gl.u2fAuthenticate.start(); ") end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 194620d0a68..a4713e53f63 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -76,7 +76,7 @@ shared_examples 'issuable record that supports slash commands in its description expect(page).not_to have_content '/assign @bob' expect(page).not_to have_content '/label ~bug' expect(page).not_to have_content '/milestone %"ASAP"' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' issuable.reload @@ -97,7 +97,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/close") expect(page).not_to have_content '/close' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(issuable.reload).to be_closed end @@ -114,7 +114,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/close") expect(page).not_to have_content '/close' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' expect(issuable).to be_open end @@ -132,7 +132,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/reopen") expect(page).not_to have_content '/reopen' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(issuable.reload).to be_open end @@ -149,7 +149,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/reopen") expect(page).not_to have_content '/reopen' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' expect(issuable).to be_closed end @@ -162,7 +162,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/title Awesome new title") expect(page).not_to have_content '/title' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(issuable.reload.title).to eq 'Awesome new title' end @@ -179,7 +179,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/title Awesome new title") expect(page).not_to have_content '/title' - expect(page).not_to have_content 'Your commands have been executed!' + expect(page).not_to have_content 'Commands applied' expect(issuable.reload.title).not_to eq 'Awesome new title' end @@ -191,7 +191,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/todo") expect(page).not_to have_content '/todo' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' todos = TodosFinder.new(master).execute todo = todos.first @@ -222,7 +222,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/done") expect(page).not_to have_content '/done' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(todo.reload).to be_done end @@ -235,7 +235,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/subscribe") expect(page).not_to have_content '/subscribe' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(issuable.subscribed?(master, project)).to be_truthy end @@ -252,7 +252,7 @@ shared_examples 'issuable record that supports slash commands in its description write_note("/unsubscribe") expect(page).not_to have_content '/unsubscribe' - expect(page).to have_content 'Your commands have been executed!' + expect(page).to have_content 'Commands applied' expect(issuable.subscribed?(master, project)).to be_falsy end diff --git a/spec/support/services/issuable_create_service_shared_examples.rb b/spec/support/services/issuable_create_service_shared_examples.rb new file mode 100644 index 00000000000..93c0267d2db --- /dev/null +++ b/spec/support/services/issuable_create_service_shared_examples.rb @@ -0,0 +1,52 @@ +shared_examples 'issuable create service' do + context 'asssignee_id' do + let(:assignee) { create(:user) } + + before { project.team << [user, :master] } + + it 'removes assignee_id when user id is invalid' do + opts = { title: 'Title', description: 'Description', assignee_id: -1 } + + issuable = described_class.new(project, user, opts).execute + + expect(issuable.assignee_id).to be_nil + end + + it 'removes assignee_id when user id is 0' do + opts = { title: 'Title', description: 'Description', assignee_id: 0 } + + issuable = described_class.new(project, user, opts).execute + + expect(issuable.assignee_id).to be_nil + end + + it 'saves assignee when user id is valid' do + project.team << [assignee, :master] + opts = { title: 'Title', description: 'Description', assignee_id: assignee.id } + + issuable = described_class.new(project, user, opts).execute + + expect(issuable.assignee_id).to eq(assignee.id) + end + + context "when issuable feature is private" do + before do + project.project_feature.update(issues_access_level: ProjectFeature::PRIVATE) + project.project_feature.update(merge_requests_access_level: ProjectFeature::PRIVATE) + end + + levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC] + + levels.each do |level| + it "removes not authorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do + project.update(visibility_level: level) + opts = { title: 'Title', description: 'Description', assignee_id: assignee.id } + + issuable = described_class.new(project, user, opts).execute + + expect(issuable.assignee_id).to be_nil + end + end + end + end +end diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb index 5f9645ed44f..dd54b0addda 100644 --- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb +++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb @@ -11,6 +11,8 @@ shared_examples 'new issuable record that supports slash commands' do let(:params) { base_params.merge(defined?(default_params) ? default_params : {}).merge(example_params) } let(:issuable) { described_class.new(project, user, params).execute } + before { project.team << [assignee, :master ] } + context 'with labels in command only' do let(:example_params) do { @@ -55,7 +57,7 @@ shared_examples 'new issuable record that supports slash commands' do context 'with assignee and milestone in params and command' do let(:example_params) do { - assignee: build_stubbed(:user), + assignee: create(:user), milestone_id: double(:milestone), description: %(/assign @#{assignee.username}\n/milestone %"#{milestone.name}") } diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb index a3336755773..49cea1e608c 100644 --- a/spec/support/services/issuable_update_service_shared_examples.rb +++ b/spec/support/services/issuable_update_service_shared_examples.rb @@ -1,4 +1,8 @@ shared_examples 'issuable update service' do + def update_issuable(opts) + described_class.new(project, user, opts).execute(open_issuable) + end + context 'changing state' do before { expect(project).to receive(:execute_hooks).once } @@ -14,4 +18,52 @@ shared_examples 'issuable update service' do end end end + + context 'asssignee_id' do + it 'does not update assignee when assignee_id is invalid' do + open_issuable.update(assignee_id: user.id) + + update_issuable(assignee_id: -1) + + expect(open_issuable.reload.assignee).to eq(user) + end + + it 'unassigns assignee when user id is 0' do + open_issuable.update(assignee_id: user.id) + + update_issuable(assignee_id: 0) + + expect(open_issuable.assignee_id).to be_nil + end + + it 'saves assignee when user id is valid' do + update_issuable(assignee_id: user.id) + + expect(open_issuable.assignee_id).to eq(user.id) + end + + it 'does not update assignee_id when user cannot read issue' do + non_member = create(:user) + original_assignee = open_issuable.assignee + + update_issuable(assignee_id: non_member.id) + + expect(open_issuable.assignee_id).to eq(original_assignee.id) + end + + context "when issuable feature is private" do + levels = [Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC] + + levels.each do |level| + it "does not update with unauthorized assignee when project is #{Gitlab::VisibilityLevel.level_name(level)}" do + assignee = create(:user) + project.update(visibility_level: level) + feature_visibility_attr = :"#{open_issuable.model_name.plural}_access_level" + project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE) + + expect{ update_issuable(assignee_id: assignee) }.not_to change{ open_issuable.assignee } + end + end + end + end end diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb index 855c28b584e..f4f63b57a5f 100644 --- a/spec/workers/project_cache_worker_spec.rb +++ b/spec/workers/project_cache_worker_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe ProjectCacheWorker do - let(:project) { create(:project) } let(:worker) { described_class.new } + let(:project) { create(:project) } + let(:statistics) { project.statistics } describe '#perform' do before do @@ -12,7 +13,7 @@ describe ProjectCacheWorker do context 'with a non-existing project' do it 'does nothing' do - expect(worker).not_to receive(:update_repository_size) + expect(worker).not_to receive(:update_statistics) worker.perform(-1) end @@ -22,24 +23,19 @@ describe ProjectCacheWorker do it 'does nothing' do allow_any_instance_of(Repository).to receive(:exists?).and_return(false) - expect(worker).not_to receive(:update_repository_size) + expect(worker).not_to receive(:update_statistics) worker.perform(project.id) end end context 'with an existing project' do - it 'updates the repository size' do - expect(worker).to receive(:update_repository_size).and_call_original - - worker.perform(project.id) - end - - it 'updates the commit count' do - expect_any_instance_of(Project).to receive(:update_commit_count). - and_call_original + it 'updates the project statistics' do + expect(worker).to receive(:update_statistics) + .with(kind_of(Project), %i(repository_size)) + .and_call_original - worker.perform(project.id) + worker.perform(project.id, [], %w(repository_size)) end it 'refreshes the method caches' do @@ -47,33 +43,35 @@ describe ProjectCacheWorker do with(%i(readme)). and_call_original - worker.perform(project.id, %i(readme)) + worker.perform(project.id, %w(readme)) end end end - describe '#update_repository_size' do + describe '#update_statistics' do context 'when a lease could not be obtained' do it 'does not update the repository size' do allow(worker).to receive(:try_obtain_lease_for). - with(project.id, :update_repository_size). + with(project.id, :update_statistics). and_return(false) - expect(project).not_to receive(:update_repository_size) + expect(statistics).not_to receive(:refresh!) - worker.update_repository_size(project) + worker.update_statistics(project) end end context 'when a lease could be obtained' do - it 'updates the repository size' do + it 'updates the project statistics' do allow(worker).to receive(:try_obtain_lease_for). - with(project.id, :update_repository_size). + with(project.id, :update_statistics). and_return(true) - expect(project).to receive(:update_repository_size).and_call_original + expect(statistics).to receive(:refresh!) + .with(only: %i(repository_size)) + .and_call_original - worker.update_repository_size(project) + worker.update_statistics(project, %i(repository_size)) end end end diff --git a/vendor/gitignore/Clojure.gitignore b/vendor/gitignore/Clojure.gitignore index 7657a270c45..a9fe6fba80d 120000..100644 --- a/vendor/gitignore/Clojure.gitignore +++ b/vendor/gitignore/Clojure.gitignore @@ -1 +1,13 @@ -Leiningen.gitignore
\ No newline at end of file +pom.xml +pom.xml.asc +*.jar +*.class +/lib/ +/classes/ +/target/ +/checkouts/ +.lein-deps-sum +.lein-repl-history +.lein-plugins/ +.lein-failures +.nrepl-port |