diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1772fda9225..e2141716311 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -273,7 +273,7 @@ rake db:migrate:reset: <<: *use-db <<: *dedicated-runner script: - - rake db:migrate:reset + - bundle exec rake db:migrate:reset rake db:seed_fu: stage: test @@ -303,7 +303,7 @@ karma: <<: *dedicated-runner script: - npm link istanbul - - rake karma + - bundle exec rake karma artifacts: name: coverage-javascript expire_in: 31d @@ -354,10 +354,10 @@ migration paths: - cp config/resque.yml.example config/resque.yml - sed -i 's/localhost/redis/g' config/resque.yml - bundle install --without postgres production --jobs $(nproc) $FLAGS --retry=3 - - rake db:drop db:create db:schema:load db:seed_fu + - bundle exec rake db:drop db:create db:schema:load db:seed_fu - git checkout $CI_BUILD_REF - source scripts/prepare_build.sh - - rake db:migrate + - bundle exec rake db:migrate coverage: stage: post-test diff --git a/CHANGELOG.md b/CHANGELOG.md index 9712b32232e..702a4ba89a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ entry. - Prevent users from deleting system deploy keys via the project deploy key API. - Upgrade omniauth gem to 1.3.2. -## 8.16.0 (2017-02-22) +## 8.16.0 (2017-01-22) - Add LDAP Rake task to rename a provider. !2181 - Validate label's title length. !5767 (Tomáš Kukrál) diff --git a/Gemfile b/Gemfile index 726d4484dc0..f9aecca70ff 100644 --- a/Gemfile +++ b/Gemfile @@ -35,7 +35,7 @@ gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth-authentiq', '~> 0.2.0' gem 'rack-oauth2', '~> 1.2.1' -gem 'jwt' +gem 'jwt', '~> 1.5.6' # Spam and anti-bot protection gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails' diff --git a/Gemfile.lock b/Gemfile.lock index b4d03eaddc2..0434fdefcd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -375,7 +375,7 @@ GEM json (1.8.3) json-schema (2.6.2) addressable (~> 2.3.8) - jwt (1.5.4) + jwt (1.5.6) kaminari (0.17.0) actionpack (>= 3.0.0) activesupport (>= 3.0.0) @@ -900,7 +900,7 @@ DEPENDENCIES jquery-rails (~> 4.1.0) jquery-ui-rails (~> 5.0.0) json-schema (~> 2.6.2) - jwt + jwt (~> 1.5.6) kaminari (~> 0.17.0) knapsack (~> 1.11.0) kubeclient (~> 2.2.0) @@ -1007,4 +1007,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.13.7 + 1.14.2 diff --git a/PROCESS.md b/PROCESS.md index cbeb781cd3c..993d60bbba8 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -12,106 +12,54 @@ etc.). ## Common actions -### Issue team +### Issue triaging -- Looks for issues without [workflow labels](#how-we-handle-issues) and triages - issue -- Closes invalid issues with a comment (duplicates, - [fixed in newer version](#issue-fixed-in-newer-version), - [issue report for old version](#issue-report-for-old-version), not a problem - in GitLab, etc.) -- Asks for feedback from issue reporter - ([invalid issue reports](#improperly-formatted-issue), - [format code](#code-format), etc.) -- Monitors all issues for feedback (but especially ones commented on since - automatically watching them) -- Closes issues with no feedback from the reporter for two weeks - -### Merge marshall & merge request coach - -- Responds to merge requests the issue team mentions them in and monitors for - new merge requests -- Provides feedback to the merge request submitter to improve the merge request - (style, tests, etc.) -- Mark merge requests `Ready for Merge` when they meet the - [contribution acceptance criteria] -- Mention developer(s) based on the - [list of members and their specialities][team] -- Closes merge requests with no feedback from the reporter for two weeks - -## Priorities of the issue team - -1. Mentioning people (critical) -1. Workflow labels (normal) -1. Functional labels (minor) -1. Assigning issues (avoid if possible) - -## Mentioning people +Our issue triage policies are [described in our handbook]. You are very welcome +to help the GitLab team triage issues. We also organize [issue bash events] once +every quarter. The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issues. Please select someone with relevant experience from -[GitLab core team][core-team]. If there is nobody mentioned with that expertise +[GitLab team][team]. If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. +[described in our handbook]: https://about.gitlab.com/handbook/engineering/issues/issue-triage-policies/ +[issue bash events]: https://gitlab.com/gitlab-org/gitlab-ce/issues/17815 + +### Merge request coaching + +Several people from the [GitLab team][team] are helping community members to get +their contributions accepted by meeting our [Definition of done][CONTRIBUTING.md#definition-of-done]. + +What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. + ## Workflow labels -Workflow labels are purposely not very detailed since that would be hard to keep -updated as you would need to re-evaluate them after every comment. We optionally -use functional labels on demand when we want to group related issues to get an -overview (for example all issues related to RVM, to tackle them in one go) and -to add details to the issue. +Labelling issues is described in the [GitLab Inc engineering workflow]. -- ~"Awaiting Feedback" Feedback pending from the reporter -- ~UX needs help from a UX designer -- ~Frontend needs help from a Front-end engineer. Please follow the - ["Implement design & UI elements" guidelines]. -- ~"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. -- ~customer is an issue reported by enterprise subscribers. This label should -be accompanied by *bug* or *feature proposal* labels. - -Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. - -## Functional labels - -These labels describe what development specialities are involved such as: `CI`, -`Core`, `Documentation`, `Frontend`, `Issues`, `Merge Requests`, `Omnibus`, -`Release`, `Repository`, `UX`. +[GitLab Inc engineering workflow]: https://about.gitlab.com/handbook/engineering/workflow/#labelling-issues ## Assigning issues If an issue is complex and needs the attention of a specific person, assignment is a good option but assigning issues might discourage other people from contributing to that issue. We need all the contributions we can get so this should never be discouraged. Also, an assigned person might not have time for a few weeks, so others should feel free to takeover. -## Label colors - -- Light orange `#fef2c0`: workflow labels for issue team members (awaiting - feedback, awaiting confirmation of fix) -- Bright orange `#eb6420`: workflow labels for core team members (attached MR, - awaiting developer action/feedback) -- Light blue `#82C5FF`: functional labels -- Green labels `#009800`: issues that can generally be ignored. For example, - issues given the following labels normally can be closed immediately: - - Support (see copy & paste response: - [Support requests and configuration questions](#support-requests-and-configuration-questions) - ## Be kind Be kind to people trying to contribute. Be aware that people may be a non-native English speaker, they might not understand things or they might be very sensitive as to how you word things. Use Emoji to express your feelings (heart, -star, smile, etc.). Some good tips about giving feedback to merge requests is in -the [Thoughtbot code review guide]. +star, smile, etc.). Some good tips about code reviews can be found in our +[Code Review Guidelines]. + +[Code Review Guidelines]: https://docs.gitlab.com/ce/development/code_review.html ## Feature Freeze -5 working days before the 22nd the stable branches for the upcoming release will +On the 7th of each month, the stable branches for the upcoming release will be frozen for major changes. Merge requests may still be merged into master during this period. By freezing the stable branches prior to a release there's no need to worry about last minute merge requests potentially breaking a lot of @@ -120,10 +68,9 @@ things. What is considered to be a major change is determined on a case by case basis as this definition depends very much on the context of changes. For example, a 5 line change might have a big impact on the entire application. Ultimately the -decision will be made by those reviewing a merge request and the release -manager. +decision will be made by the maintainers and the release managers. -During the feature freeze all merge requests that are meant to go into the next +During the feature freeze all merge requests that are meant to go into the upcoming release should have the correct milestone assigned _and_ have the label ~"Pick into Stable" set. Merge requests without a milestone and this label will not be merged into any stable branches. @@ -189,7 +136,6 @@ prevent duplication with the GitLab.com issue tracker. Since this is an older issue I'll be closing this for now. If you think this is still an issue I encourage you to open it on the \[GitLab.com issue tracker\]\(https://gitlab.com/gitlab-org/gitlab-ce/issues). -[core-team]: https://about.gitlab.com/core-team/ [team]: https://about.gitlab.com/team/ [contribution acceptance criteria]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#contribution-acceptance-criteria ["Implement design & UI elements" guidelines]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#implement-design-ui-elements diff --git a/app/assets/javascripts/behaviors/autosize.js b/app/assets/javascripts/behaviors/autosize.js index 10d5275cc7c..a489523b802 100644 --- a/app/assets/javascripts/behaviors/autosize.js +++ b/app/assets/javascripts/behaviors/autosize.js @@ -2,7 +2,6 @@ /* global autosize */ var autosize = require('vendor/autosize'); -require('vendor/jquery.ba-resize'); (function() { $(function() { diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js.es6 index 02459722bbf..75dfcb66bb0 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js.es6 +++ b/app/assets/javascripts/boards/components/board_sidebar.js.es6 @@ -29,6 +29,12 @@ watch: { detail: { handler () { + if (this.issue.id !== this.detail.issue.id) { + $('.js-issue-board-sidebar', this.$el).each((i, el) => { + $(el).data('glDropdown').clearMenu(); + }); + } + this.issue = this.detail.issue; }, deep: true diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js.es6 index 529d476ca4e..edec21e3b63 100644 --- a/app/assets/javascripts/dispatcher.js.es6 +++ b/app/assets/javascripts/dispatcher.js.es6 @@ -8,7 +8,6 @@ /* global ShortcutsIssuable */ /* global ZenMode */ /* global Milestone */ -/* global GLForm */ /* global IssuableForm */ /* global LabelsSelect */ /* global MilestoneSelect */ @@ -64,17 +63,6 @@ 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(); @@ -110,7 +98,7 @@ case 'projects:milestones:edit': new ZenMode(); new gl.DueDateSelectors(); - new GLForm($('.milestone-form')); + new gl.GLForm($('.milestone-form')); break; case 'groups:milestones:new': new ZenMode(); @@ -121,7 +109,7 @@ case 'projects:issues:new': case 'projects:issues:edit': shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.issue-form')); + new gl.GLForm($('.issue-form')); new IssuableForm($('.issue-form')); new LabelsSelect(); new MilestoneSelect(); @@ -131,7 +119,7 @@ case 'projects:merge_requests:edit': new gl.Diff(); shortcut_handler = new ShortcutsNavigation(); - new GLForm($('.merge-request-form')); + new gl.GLForm($('.merge-request-form')); new IssuableForm($('.merge-request-form')); new LabelsSelect(); new MilestoneSelect(); @@ -139,11 +127,11 @@ break; case 'projects:tags:new': new ZenMode(); - new GLForm($('.tag-form')); + new gl.GLForm($('.tag-form')); break; case 'projects:releases:edit': new ZenMode(); - new GLForm($('.release-form')); + new gl.GLForm($('.release-form')); break; case 'projects:merge_requests:show': new gl.Diff(); @@ -280,6 +268,17 @@ break; } switch (path.first()) { + case 'sessions': + case 'omniauth_callbacks': + if (!gon.u2f) break; + 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'), + ); + gl.u2fAuthenticate.start(); case 'admin': new Admin(); switch (path[1]) { @@ -332,7 +331,7 @@ new gl.Wikis(); shortcut_handler = new ShortcutsNavigation(); new ZenMode(); - new GLForm($('.wiki-form')); + new gl.GLForm($('.wiki-form')); break; case 'snippets': shortcut_handler = new ShortcutsNavigation(); @@ -357,7 +356,7 @@ } // If we haven't installed a custom shortcut handler, install the default one if (!shortcut_handler) { - return new Shortcuts(); + new Shortcuts(); } }; diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index e148547cead..5c86e98567a 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -512,12 +512,17 @@ // Append the menu into the dropdown GitLabDropdown.prototype.appendMenu = function(html) { + return this.clearMenu().append(html); + }; + + GitLabDropdown.prototype.clearMenu = function() { var selector; selector = '.dropdown-content'; if (this.dropdown.find(".dropdown-toggle-page").length) { selector = ".dropdown-page-one .dropdown-content"; } - return $(selector, this.dropdown).empty().append(html); + + return $(selector, this.dropdown).empty(); }; GitLabDropdown.prototype.renderItem = function(data, group, index) { diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js deleted file mode 100644 index 73146b28b03..00000000000 --- a/app/assets/javascripts/gl_form.js +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */ -/* global GitLab */ -/* global DropzoneInput */ -/* global autosize */ - -var autosize = require('vendor/autosize'); - -(function() { - this.GLForm = (function() { - function GLForm(form) { - this.form = form; - this.textarea = this.form.find('textarea.js-gfm-input'); - // Before we start, we should clean up any previous data for this form - this.destroy(); - // Setup the form - this.setupForm(); - this.form.data('gl-form', this); - } - - GLForm.prototype.destroy = function() { - // Clean form listeners - this.clearEventListeners(); - return this.form.data('gl-form', null); - }; - - GLForm.prototype.setupForm = function() { - var isNewForm; - isNewForm = this.form.is(':not(.gfm-form)'); - this.form.removeClass('js-new-note-form'); - if (isNewForm) { - this.form.find('.div-dropzone').remove(); - this.form.addClass('gfm-form'); - // remove notify commit author checkbox for non-commit notes - gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); - gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); - new DropzoneInput(this.form); - autosize(this.textarea); - // form and textarea event listeners - this.addEventListeners(); - } - gl.text.init(this.form); - // hide discard button - this.form.find('.js-note-discard').hide(); - return this.form.show(); - }; - - GLForm.prototype.clearEventListeners = function() { - this.textarea.off('focus'); - this.textarea.off('blur'); - return gl.text.removeListeners(this.form); - }; - - GLForm.prototype.addEventListeners = function() { - this.textarea.on('focus', function() { - return $(this).closest('.md-area').addClass('is-focused'); - }); - return this.textarea.on('blur', function() { - return $(this).closest('.md-area').removeClass('is-focused'); - }); - }; - - return GLForm; - })(); -}).call(this); diff --git a/app/assets/javascripts/gl_form.js.es6 b/app/assets/javascripts/gl_form.js.es6 new file mode 100644 index 00000000000..0b446ff364a --- /dev/null +++ b/app/assets/javascripts/gl_form.js.es6 @@ -0,0 +1,92 @@ +/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, no-new, max-len */ +/* global GitLab */ +/* global DropzoneInput */ +/* global autosize */ + +(() => { + const global = window.gl || (window.gl = {}); + + function GLForm(form) { + this.form = form; + this.textarea = this.form.find('textarea.js-gfm-input'); + // Before we start, we should clean up any previous data for this form + this.destroy(); + // Setup the form + this.setupForm(); + this.form.data('gl-form', this); + } + + GLForm.prototype.destroy = function() { + // Clean form listeners + this.clearEventListeners(); + return this.form.data('gl-form', null); + }; + + GLForm.prototype.setupForm = function() { + var isNewForm; + isNewForm = this.form.is(':not(.gfm-form)'); + this.form.removeClass('js-new-note-form'); + if (isNewForm) { + this.form.find('.div-dropzone').remove(); + this.form.addClass('gfm-form'); + // remove notify commit author checkbox for non-commit notes + gl.utils.disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); + gl.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); + new DropzoneInput(this.form); + autosize(this.textarea); + // form and textarea event listeners + this.addEventListeners(); + } + gl.text.init(this.form); + // hide discard button + this.form.find('.js-note-discard').hide(); + this.form.show(); + if (this.isAutosizeable) this.setupAutosize(); + }; + + GLForm.prototype.setupAutosize = function () { + this.textarea.off('autosize:resized') + .on('autosize:resized', this.setHeightData.bind(this)); + + this.textarea.off('mouseup.autosize') + .on('mouseup.autosize', this.destroyAutosize.bind(this)); + + setTimeout(() => { + autosize(this.textarea); + this.textarea.css('resize', 'vertical'); + }, 0); + }; + + GLForm.prototype.setHeightData = function () { + this.textarea.data('height', this.textarea.outerHeight()); + }; + + GLForm.prototype.destroyAutosize = function () { + const outerHeight = this.textarea.outerHeight(); + + if (this.textarea.data('height') === outerHeight) return; + + autosize.destroy(this.textarea); + + this.textarea.data('height', outerHeight); + this.textarea.outerHeight(outerHeight); + this.textarea.css('max-height', window.outerHeight); + }; + + GLForm.prototype.clearEventListeners = function() { + this.textarea.off('focus'); + this.textarea.off('blur'); + return gl.text.removeListeners(this.form); + }; + + GLForm.prototype.addEventListeners = function() { + this.textarea.on('focus', function() { + return $(this).closest('.md-area').addClass('is-focused'); + }); + return this.textarea.on('blur', function() { + return $(this).closest('.md-area').removeClass('is-focused'); + }); + }; + + global.GLForm = GLForm; +})(); diff --git a/app/assets/javascripts/groups_select.js b/app/assets/javascripts/groups_select.js index a50bc4a9057..bc88dc2d092 100644 --- a/app/assets/javascripts/groups_select.js +++ b/app/assets/javascripts/groups_select.js @@ -59,11 +59,11 @@ } else { avatar = gon.default_avatar_url; } - return "
" + group.name + "
" + group.path + "
"; + return "
" + group.full_name + "
" + group.full_path + "
"; }; GroupsSelect.prototype.formatSelection = function(group) { - return group.name; + return group.full_name; }; return GroupsSelect; diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6 index 8f48b1f57ce..2a50b72c8aa 100644 --- a/app/assets/javascripts/label_manager.js.es6 +++ b/app/assets/javascripts/label_manager.js.es6 @@ -8,6 +8,7 @@ this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); this.otherLabels = otherLabels || $('.js-other-labels'); this.errorMessage = 'Unable to update label prioritization at this time'; + this.emptyState = document.querySelector('#js-priority-labels-empty-state'); this.prioritizedLabels.sortable({ items: 'li', placeholder: 'list-placeholder', @@ -29,7 +30,12 @@ const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); + _this.toggleLabelPriority($label, action); + _this.toggleEmptyState($label, $btn, action); + } + + toggleEmptyState($label, $btn, action) { + this.emptyState.classList.toggle('hidden', !!this.prioritizedLabels[0].querySelector(':scope > li')); } toggleLabelPriority($label, action, persistState) { diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index fbe235a958e..d108da29af7 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,6 +1,5 @@ /* eslint-disable no-restricted-properties, func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, no-use-before-define, camelcase, no-unused-expressions, quotes, max-len, one-var, one-var-declaration-per-line, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, comma-dangle, no-new, brace-style, no-lonely-if, vars-on-top, no-unused-vars, no-sequences, no-shadow, newline-per-chained-call, no-useless-escape */ /* global Flash */ -/* global GLForm */ /* global Autosave */ /* global ResolveService */ /* global mrRefreshWidgetUrl */ @@ -421,7 +420,7 @@ require('vendor/task_list'); Notes.prototype.setupNoteForm = function(form) { var textarea; - new GLForm(form); + new gl.GLForm(form); textarea = form.find(".js-note-text"); return new Autosave(textarea, ["Note", form.find("#note_noteable_type").val(), form.find("#note_noteable_id").val(), form.find("#note_commit_id").val(), form.find("#note_type").val(), form.find("#note_line_code").val(), form.find("#note_position").val()]); }; @@ -885,7 +884,7 @@ require('vendor/task_list'); var targetId = $originalContentEl.data('target-id'); var targetType = $originalContentEl.data('target-type'); - new GLForm($editForm.find('form')); + new gl.GLForm($editForm.find('form')); $editForm.find('form') .attr('action', postUrl) diff --git a/app/assets/javascripts/search.js b/app/assets/javascripts/search.js index 489e567259c..b1c0dc37b4d 100644 --- a/app/assets/javascripts/search.js +++ b/app/assets/javascripts/search.js @@ -13,12 +13,12 @@ filterable: true, fieldName: 'group_id', search: { - fields: ['name'] + fields: ['full_name'] }, data: function(term, callback) { return Api.groups(term, {}, function(data) { data.unshift({ - name: 'Any' + full_name: 'Any' }); data.splice(1, 0, 'divider'); return callback(data); @@ -28,10 +28,10 @@ return obj.id; }, text: function(obj) { - return obj.name; + return obj.full_name; }, toggleLabel: function(obj) { - return ($groupDropdown.data('default-label')) + " " + obj.name; + return ($groupDropdown.data('default-label')) + " " + obj.full_name; }, clicked: (function(_this) { return function() { diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js.es6 index ef9c0a885fb..05622916ff8 100644 --- a/app/assets/javascripts/todos.js.es6 +++ b/app/assets/javascripts/todos.js.es6 @@ -85,7 +85,7 @@ }, success: (data) => { $target.remove(); - $('.prepend-top-default').html('
You\'re all done!
'); + $('.js-todos-all').html('
You\'re all done!
'); return this.updateBadges(data); } }); diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 592ef0d647f..0f9213b98e3 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -278,6 +278,10 @@ display: inline-block; } + .btn { + margin: $btn-side-margin $btn-side-margin 0 0; + } + @media(max-width: $screen-xs-max) { margin-top: 50px; text-align: center; @@ -286,6 +290,12 @@ width: 100%; } } + + @media(min-width: $screen-xs-max) { + &.labels .text-content { + margin-top: 70px; + } + } } .flex-container-block { diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 12d56359d7d..ea2d26dd5a0 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -162,6 +162,10 @@ } } } + + &.panel-without-border { + border: 0; + } } .panel-succes .panel-heading, diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 47dfc22d533..cf79c2e36c2 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -494,31 +494,27 @@ // Action Icons in big pipeline-graph nodes > .ci-action-icon-container .ci-action-icon-wrapper { - i { - color: $border-color; - border-radius: 100%; - border: 1px solid $border-color; - padding: 5px 6px; - font-size: 13px; - background: $white-light; - height: 30px; - width: 30px; + height: 30px; + width: 30px; + background: $white-light; + border: 1px solid $border-color; + border-radius: 100%; + display: block; - &::before { - position: relative; - top: 3px; - left: 3px; - } - - &:hover { - color: $gl-text-color; - background-color: $stage-hover-bg; - border: 1px solid $stage-hover-bg; - } + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $stage-hover-bg; } - .ci-play-icon { - padding: 5px 5px 5px 7px; + svg { + fill: $border-color; + position: relative; + left: -1px; + top: -1px; + } + + &:hover svg { + fill: $gl-text-color; } } @@ -657,7 +653,7 @@ font-weight: 100; font-size: 15px; position: absolute; - right: 5px; + right: 13px; top: 8px; } @@ -825,11 +821,23 @@ &:hover, &:focus { - text-decoration: none; - color: $gl-text-color; background-color: $stage-hover-bg; border: 1px solid transparent; } + + svg { + width: 22px; + height: 22px; + left: -6px; + position: relative; + top: -3px; + fill: $action-icon-color; + } + + &:hover svg, + &:focus svg { + fill: $gl-text-color; + } } // link to the build diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 01675acc62e..0d5604aae69 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -76,6 +76,10 @@ font-size: 14px; } + .action-name { + font-weight: normal; + } + .todo-body { .todo-note { word-wrap: break-word; diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4dda4e51f6a..79d420a32d3 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -4,6 +4,7 @@ class DashboardController < Dashboard::ApplicationController before_action :event_filter, only: :activity before_action :projects, only: [:issues, :merge_requests] + before_action :set_show_full_reference, only: [:issues, :merge_requests] respond_to :html @@ -34,4 +35,8 @@ class DashboardController < Dashboard::ApplicationController @events = @event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end + + def set_show_full_reference + @show_full_reference = true + end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index bfc59bcc862..c871043efbd 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -30,6 +30,17 @@ class Projects::CommitController < Projects::ApplicationController end def pipelines + @pipelines = @commit.pipelines.order(id: :desc) + + respond_to do |format| + format.html + format.json do + render json: PipelineSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .represent(@pipelines) + end + end end def branches diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 824ed7be73e..1593b5c1afb 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -2,12 +2,13 @@ class Projects::LabelsController < Projects::ApplicationController include ToggleSubscriptionAction before_action :module_enabled - before_action :label, only: [:edit, :update, :destroy] + before_action :label, only: [:edit, :update, :destroy, :promote] before_action :find_labels, only: [:index, :set_priorities, :remove_priority, :toggle_subscription] before_action :authorize_read_label! before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :generate, :destroy, :remove_priority, :set_priorities] + before_action :authorize_admin_group!, only: [:promote] respond_to :js, :html @@ -71,13 +72,7 @@ class Projects::LabelsController < Projects::ApplicationController @label.destroy @labels = find_labels - respond_to do |format| - format.html do - redirect_to(namespace_project_labels_path(@project.namespace, @project), - notice: 'Label was removed') - end - format.js - end + redirect_to(namespace_project_labels_path(@project.namespace, @project), notice: 'Label was removed') end def remove_priority @@ -108,6 +103,32 @@ class Projects::LabelsController < Projects::ApplicationController end end + def promote + promote_service = Labels::PromoteService.new(@project, @current_user) + + begin + return render_404 unless promote_service.execute(@label) + respond_to do |format| + format.html do + redirect_to(namespace_project_labels_path(@project.namespace, @project), + notice: 'Label was promoted to a Group Label') + end + format.js + end + rescue ActiveRecord::RecordInvalid => e + Gitlab::AppLogger.error "Failed to promote label \"#{@label.title}\" to group label" + Gitlab::AppLogger.error e + + respond_to do |format| + format.html do + redirect_to(namespace_project_labels_path(@project.namespace, @project), + notice: 'Failed to promote label due to internal error. Please contact administrators.') + end + format.js + end + end + end + protected def module_enabled @@ -135,4 +156,8 @@ class Projects::LabelsController < Projects::ApplicationController def authorize_admin_labels! return render_404 unless can?(current_user, :admin_label, @project) end + + def authorize_admin_group! + return render_404 unless can?(current_user, :admin_group, @project.group) + end end diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb index 01d99c7df35..38f7e6eb5e9 100644 --- a/app/controllers/projects/mattermosts_controller.rb +++ b/app/controllers/projects/mattermosts_controller.rb @@ -34,7 +34,7 @@ class Projects::MattermostsController < Projects::ApplicationController end def teams - @teams ||= @service.list_teams(current_user) + @teams, @teams_error_message = @service.list_teams(current_user) end def service diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 9ac5bf4b9f8..3492502e296 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -214,7 +214,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController render 'show' end - format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_pipelines') } } + + format.json do + render json: { + html: view_to_html_string('projects/merge_requests/show/_pipelines'), + pipelines: PipelineSerializer + .new(project: @project, user: @current_user) + .with_pagination(request, response) + .represent(@pipelines) + } + end end end diff --git a/app/controllers/root_controller.rb b/app/controllers/root_controller.rb index 627be74a38f..db2817fadf6 100644 --- a/app/controllers/root_controller.rb +++ b/app/controllers/root_controller.rb @@ -7,6 +7,7 @@ # For users who haven't customized the setting, we simply delegate to # `DashboardController#show`, which is the default. class RootController < Dashboard::ProjectsController + skip_before_action :authenticate_user!, only: [:index] before_action :redirect_to_custom_dashboard, only: [:index] def index @@ -16,7 +17,7 @@ class RootController < Dashboard::ProjectsController private def redirect_to_custom_dashboard - return unless current_user + return redirect_to new_user_session_path unless current_user case current_user.dashboard when 'stars' diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index e5bb8b93e76..03354c235eb 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -162,6 +162,10 @@ module IssuablesHelper ] end + def issuable_reference(issuable) + @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project) + end + def issuable_filter_present? issuable_filter_params.any? { |k| params.key?(k) } end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 6654f6997ce..37b69423c97 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -89,7 +89,7 @@ module SearchHelper { category: "Groups", id: group.id, - label: "#{search_result_sanitize(group.name)}", + label: "#{search_result_sanitize(group.full_name)}", url: group_path(group) } end diff --git a/app/models/commit.rb b/app/models/commit.rb index 316bd2e512b..46f06733da1 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -100,8 +100,8 @@ class Commit commit_reference(from_project, id, full: full) end - def reference_link_text(from_project = nil) - commit_reference(from_project, short_id) + def reference_link_text(from_project = nil, full: false) + commit_reference(from_project, short_id, full: full) end def diff_line_count diff --git a/app/models/environment.rb b/app/models/environment.rb index 652abf18a8a..577367f1eed 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -1,7 +1,8 @@ class Environment < ActiveRecord::Base # Used to generate random suffixes for the slug + LETTERS = 'a'..'z' NUMBERS = '0'..'9' - SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a + SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a belongs_to :project, required: true, validate: true @@ -148,17 +149,24 @@ class Environment < ActiveRecord::Base slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-') # Must start with a letter - slugified = "env-" + slugified if NUMBERS.cover?(slugified[0]) + slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0]) + + # Repeated dashes are invalid (OpenShift limitation) + slugified.gsub!(/\-+/, '-') # Maximum length: 24 characters (OpenShift limitation) slugified = slugified[0..23] - # Cannot end with a "-" character (Kubernetes label limitation) - slugified = slugified[0..-2] if slugified[-1] == "-" + # Cannot end with a dash (Kubernetes label limitation) + slugified.chop! if slugified.end_with?('-') # Add a random suffix, shortening the current string if necessary, if it # has been slugified. This ensures uniqueness. - slugified = slugified[0..16] + "-" + random_suffix if slugified != name + if slugified != name + slugified = slugified[0..16] + slugified << '-' unless slugified.end_with?('-') + slugified << random_suffix + end self.slug = slugified end diff --git a/app/models/issue.rb b/app/models/issue.rb index 65638d9a299..d8826b65fcc 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -97,10 +97,11 @@ class Issue < ActiveRecord::Base end end - def to_reference(from_project = nil, full: false) + # `from` argument can be a Namespace or Project. + def to_reference(from = nil, full: false) reference = "#{self.class.reference_prefix}#{iid}" - "#{project.to_reference(from_project, full: full)}#{reference}" + "#{project.to_reference(from, full: full)}#{reference}" end def referenced_merge_requests(current_user = nil) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6753504acff..082adcafcc8 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -179,10 +179,11 @@ class MergeRequest < ActiveRecord::Base work_in_progress?(title) ? title : "WIP: #{title}" end - def to_reference(from_project = nil, full: false) + # `from` argument can be a Namespace or Project. + def to_reference(from = nil, full: false) reference = "#{self.class.reference_prefix}#{iid}" - "#{project.to_reference(from_project, full: full)}#{reference}" + "#{project.to_reference(from, full: full)}#{reference}" end def first_commit diff --git a/app/models/project.rb b/app/models/project.rb index 59faf35e051..37f4705adbd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -225,6 +225,7 @@ class Project < ActiveRecord::Base scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_statistics, -> { includes(:statistics) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) } + scope :inside_path, ->(path) { joins(:route).where('routes.path LIKE ?', "#{path}/%") } # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { @@ -591,10 +592,11 @@ class Project < ActiveRecord::Base end end - def to_reference(from_project = nil, full: false) - if full || cross_namespace_reference?(from_project) + # `from` argument can be a Namespace or Project. + def to_reference(from = nil, full: false) + if full || cross_namespace_reference?(from) path_with_namespace - elsif cross_project_reference?(from_project) + elsif cross_project_reference?(from) path end end @@ -1291,21 +1293,26 @@ class Project < ActiveRecord::Base private + def cross_namespace_reference?(from) + case from + when Project + namespace != from.namespace + when Namespace + namespace != from + end + end + # Check if a reference is being done cross-project - # - # from_project - Refering Project object - def cross_project_reference?(from_project) - from_project && self != from_project + def cross_project_reference?(from) + return true if from.is_a?(Namespace) + + from && self != from end def pushes_since_gc_redis_key "projects/#{id}/pushes_since_gc" end - def cross_namespace_reference?(from_project) - from_project && namespace != from_project.namespace - end - def default_branch_protected? current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL || current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 2ac76e97de0..80d002f9c32 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -60,9 +60,9 @@ class JiraService < IssueTrackerService end def help - 'You need to configure JIRA before enabling this service. For more details + "You need to configure JIRA before enabling this service. For more details read the - [JIRA service documentation](https://docs.gitlab.com/ce/project_services/jira.html).' + [JIRA service documentation](#{help_page_url('project_services/jira')})." end def title diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 50a011db74e..b0f7a42f9a3 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -28,8 +28,8 @@ class MattermostSlashCommandsService < ChatSlashCommandsService [false, e.message] end - def list_teams(user) - Mattermost::Team.new(user).all + def list_teams(current_user) + [Mattermost::Team.new(current_user).all, nil] rescue Mattermost::Error => e [[], e.message] end diff --git a/app/models/todo.rb b/app/models/todo.rb index 4c99aa0d3be..2adf494ce11 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -103,9 +103,9 @@ class Todo < ActiveRecord::Base def target_reference if for_commit? - target.short_id + target.reference_link_text(full: true) else - target.to_reference + target.to_reference(full: true) end end diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb index 7b1752df0e1..8b25332b73c 100644 --- a/app/policies/ci/build_policy.rb +++ b/app/policies/ci/build_policy.rb @@ -1,8 +1,6 @@ module Ci class BuildPolicy < CommitStatusPolicy def rules - can! :read_build if @subject.project.public_builds? - super # If we can't read build we should also not have that diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb index de9a181db90..311ee9c96be 100644 --- a/app/serializers/base_serializer.rb +++ b/app/serializers/base_serializer.rb @@ -6,6 +6,7 @@ class BaseSerializer def represent(resource, opts = {}) self.class.entity_class .represent(resource, opts.merge(request: @request)) + .as_json end def self.entity(entity_class) diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index cfa86cc2553..b2de6c5832e 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -1,9 +1,10 @@ class PipelineSerializer < BaseSerializer - entity PipelineEntity class InvalidResourceError < StandardError; end include API::Helpers::Pagination Struct.new('Pagination', :request, :response) + entity PipelineEntity + def represent(resource, opts = {}) if paginated? raise InvalidResourceError unless resource.respond_to?(:page) diff --git a/app/services/labels/promote_service.rb b/app/services/labels/promote_service.rb new file mode 100644 index 00000000000..76d0ba67b07 --- /dev/null +++ b/app/services/labels/promote_service.rb @@ -0,0 +1,71 @@ +module Labels + class PromoteService < BaseService + BATCH_SIZE = 1000 + + def execute(label) + return unless project.group && + label.is_a?(ProjectLabel) + + Label.transaction do + new_label = clone_label_to_group_label(label) + + label_ids_for_merge(new_label).find_in_batches(batch_size: BATCH_SIZE) do |batched_ids| + update_issuables(new_label, batched_ids) + update_issue_board_lists(new_label, batched_ids) + update_priorities(new_label, batched_ids) + # Order is important, project labels need to be last + update_project_labels(batched_ids) + end + + # We skipped validations during creation. Let's run them now, after deleting conflicting labels + raise ActiveRecord::RecordInvalid.new(new_label) unless new_label.valid? + new_label + end + end + + private + + def label_ids_for_merge(new_label) + LabelsFinder. + new(current_user, title: new_label.title, group_id: project.group.id). + execute(skip_authorization: true). + where.not(id: new_label). + select(:id) # Can't use pluck() to avoid object-creation because of the batching + end + + def update_issuables(new_label, label_ids) + LabelLink. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_issue_board_lists(new_label, label_ids) + List. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_priorities(new_label, label_ids) + LabelPriority. + where(label: label_ids). + update_all(label_id: new_label) + end + + def update_project_labels(label_ids) + Label.where(id: label_ids).delete_all + end + + def clone_label_to_group_label(label) + params = label.attributes.slice('title', 'description', 'color') + # Since the title of the new label has to be the same as the previous labels + # and we're merging old labels in batches we'll skip validation to omit 2-step + # merge process and do it in one batch + # We'll be forcing validation at the end of the transaction to ensure everything + # was merged correctly + new_label = GroupLabel.new(params.merge(group: project.group)) + new_label.save(validate: false) + + new_label + end + end +end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 6a7393a9921..1d6d2754559 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -1,62 +1,88 @@ module MergeRequests class BuildService < MergeRequests::BaseService def execute - merge_request = MergeRequest.new(params) - - # Set MR attributes - merge_request.can_be_created = true + self.merge_request = MergeRequest.new(params) + merge_request.can_be_created = true merge_request.compare_commits = [] - merge_request.source_project = project unless merge_request.source_project + merge_request.source_project = find_source_project + merge_request.target_project = find_target_project + merge_request.target_branch = find_target_branch - merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project) + if branches_specified? && branches_valid? + compare_branches + assign_title_and_description + else + merge_request.can_be_created = false + end - merge_request.target_project ||= (project.forked_from_project || project) - merge_request.target_branch ||= merge_request.target_project.default_branch - - messages = validate_branches(merge_request) - return build_failed(merge_request, messages) unless messages.empty? - - compare = CompareService.new.execute( - merge_request.source_project, - merge_request.source_branch, - merge_request.target_project, - merge_request.target_branch, - ) - - merge_request.compare_commits = compare.commits - merge_request.compare = compare - - set_title_and_description(merge_request) + merge_request end private - def validate_branches(merge_request) - messages = [] + attr_accessor :merge_request - if merge_request.target_branch.blank? || merge_request.source_branch.blank? - messages << - if params[:source_branch] || params[:target_branch] - "You must select source and target branch" - end - end + delegate :target_branch, :source_branch, :source_project, :target_project, :compare_commits, :wip_title, :description, :errors, to: :merge_request - if merge_request.source_project == merge_request.target_project && - merge_request.target_branch == merge_request.source_branch + def find_source_project + source_project || project + end - messages << 'You must select different branches' - end + def find_target_project + return target_project if target_project.present? && can?(current_user, :read_project, target_project) + project.forked_from_project || project + end - # See if source and target branches exist - if merge_request.source_branch.present? && !merge_request.source_project.commit(merge_request.source_branch) - messages << "Source branch \"#{merge_request.source_branch}\" does not exist" - end + def find_target_branch + target_branch || target_project.default_branch + end - if merge_request.target_branch.present? && !merge_request.target_project.commit(merge_request.target_branch) - messages << "Target branch \"#{merge_request.target_branch}\" does not exist" - end + def branches_specified? + params[:source_branch] && params[:target_branch] + end - messages + def branches_valid? + validate_branches + errors.blank? + end + + def compare_branches + compare = CompareService.new.execute( + source_project, + source_branch, + target_project, + target_branch + ) + + merge_request.compare_commits = compare.commits + merge_request.compare = compare + end + + def validate_branches + add_error('You must select source and target branch') unless branches_present? + add_error('You must select different branches') if same_source_and_target? + add_error("Source branch \"#{source_branch}\" does not exist") unless source_branch_exists? + add_error("Target branch \"#{target_branch}\" does not exist") unless target_branch_exists? + end + + def add_error(message) + errors.add(:base, message) + end + + def branches_present? + target_branch.present? && source_branch.present? + end + + def same_source_and_target? + source_project == target_project && target_branch == source_branch + end + + def source_branch_exists? + source_branch.blank? || source_project.commit(source_branch) + end + + def target_branch_exists? + target_branch.blank? || target_project.commit(target_branch) end # When your branch name starts with an iid followed by a dash this pattern will be @@ -71,17 +97,17 @@ module MergeRequests # - Setting the title as 'Resolves "Emoji don't show up in commit title"' if there is # more than one commit in the MR # - def set_title_and_description(merge_request) - if match = merge_request.source_branch.match(/\A(\d+)-/) + def assign_title_and_description + if match = source_branch.match(/\A(\d+)-/) iid = match[1] end - commits = merge_request.compare_commits + commits = compare_commits if commits && commits.count == 1 commit = commits.first merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) - elsif iid && issue = merge_request.target_project.get_issue(iid, current_user) + elsif iid && issue = target_project.get_issue(iid, current_user) case issue when Issue merge_request.title = "Resolve \"#{issue.title}\"" @@ -89,31 +115,20 @@ module MergeRequests merge_request.title = "Resolve #{issue.title}" end else - merge_request.title = merge_request.source_branch.titleize.humanize + merge_request.title = source_branch.titleize.humanize end if iid closes_issue = "Closes ##{iid}" - if merge_request.description.present? + if description.present? merge_request.description += closes_issue.prepend("\n\n") else merge_request.description = closes_issue end end - merge_request.title = merge_request.wip_title if commits.empty? - - merge_request - end - - def build_failed(merge_request, messages) - messages.compact.each do |message| - merge_request.errors.add(:base, message) - end - merge_request.compare_commits = [] - merge_request.can_be_created = false - merge_request + merge_request.title = wip_title if commits.empty? end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index f74e6cac174..b2cc39763f3 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -365,7 +365,7 @@ class NotificationService users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) - users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users) + users_with_group_setting = select_group_member_setting(project.group, project_members, users_with_group_level_global, users) User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a end @@ -415,8 +415,8 @@ class NotificationService end # Build a list of users based on group notification settings - def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) - uids = notification_settings_for(project, :watch) + def select_group_member_setting(group, project_members, global_setting, users_global_level_watch) + uids = notification_settings_for(group, :watch) # Group setting is watch, add to users list if user is not project member users = [] @@ -473,7 +473,7 @@ class NotificationService setting = user.notification_settings_for(project) - if !setting && project.group + if project.group && (setting.nil? || setting.global?) setting = user.notification_settings_for(project.group) end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index aa9837038a6..781cd13b44b 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -9,7 +9,10 @@ module Search def execute group = Group.find_by(id: params[:group_id]) if params[:group_id].present? projects = ProjectsFinder.new.execute(current_user) - projects = projects.in_namespace(group.id) if group + + if group + projects = projects.inside_path(group.full_path) + end Gitlab::SearchResults.new(current_user, projects, params[:search]) end diff --git a/app/views/ci/status/_dropdown_graph_badge.html.haml b/app/views/ci/status/_dropdown_graph_badge.html.haml index 8dea3479f82..8ed23ac4919 100644 --- a/app/views/ci/status/_dropdown_graph_badge.html.haml +++ b/app/views/ci/status/_dropdown_graph_badge.html.haml @@ -16,4 +16,4 @@ - if status.has_action? = link_to status.action_path, class: 'ci-action-icon-wrapper js-ci-action-icon', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do - = icon(status.action_icon, class: status.action_class) + = custom_icon(status.action_icon) diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml index dd2f649de9a..0530d21a7e2 100644 --- a/app/views/ci/status/_graph_badge.html.haml +++ b/app/views/ci/status/_graph_badge.html.haml @@ -2,7 +2,7 @@ - subject = local_assigns.fetch(:subject) - status = subject.detailed_status(current_user) -- klass = "ci-status-icon ci-status-icon-#{status.group}" +- klass = "ci-status-icon ci-status-icon-#{status.group} js-ci-status-icon-#{status.group}" - tooltip = "#{subject.name} - #{status.label}" - if status.has_details? @@ -16,5 +16,5 @@ - if status.has_action? = link_to status.action_path, class: 'ci-action-icon-container has-tooltip', method: status.action_method, data: { toggle: 'tooltip', title: status.action_title } do - %i.ci-action-icon-wrapper - = icon(status.action_icon, class: status.action_class) + %i.ci-action-icon-wrapper{ class: "js-#{status.action_icon.dasherize}" } + = custom_icon(status.action_icon) diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 3caaf827ff5..653052f7c54 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -15,6 +15,4 @@ = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/issuable/filter', type: :issues - -.prepend-top-default - = render 'shared/issues' += render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index fb016599fef..e64c78c4cb8 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -7,6 +7,4 @@ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/issuable/filter', type: :merge_requests - -.prepend-top-default - = render 'shared/merge_requests' += render 'shared/merge_requests' diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 9d7bcdb9d16..605bfd0cf8d 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -11,8 +11,11 @@ = link_to_author(todo) - else (removed) - %span.todo-label + + %span.action-name = todo_action_name(todo) + + %span.todo-label - if todo.target = todo_target_link(todo) - else diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index f4efcfb27b2..c4bf2c90cc2 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -67,21 +67,17 @@ = sort_title_oldest_created -.prepend-top-default +.js-todos-all - if @todos.any? .js-todos-options{ data: {per_page: @todos.limit_value, current_page: @todos.current_page, total_pages: @todos.total_pages} } - - @todos.group_by(&:project).each do |group| - .panel.panel-default.panel-small - - project = group[0] - .panel-heading - = link_to project.name_with_namespace, namespace_project_path(project.namespace, project) - + .panel.panel-default.panel-small.panel-without-border %ul.content-list.todos-list - = render group[1] + = render @todos = paginate @todos, theme: "gitlab" + - elsif current_user.todos.any? .todos-all-done - = render "shared/empty_states/todos_all_done.svg" + = render "shared/empty_states/icons/todos_all_done.svg" - if todos_filter_empty? %h4.text-center = Gitlab.config.gitlab.no_todos_messages.sample @@ -98,7 +94,7 @@ - else .todos-empty .todos-empty-hero - = render "shared/empty_states/todos_empty.svg" + = render "shared/empty_states/icons/todos_empty.svg" .todos-empty-content %h4 Todos let you see what you should do next. diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 6ad03a60b3a..83edb719692 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -23,7 +23,6 @@ - if current_user To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. - .prepend-top-default - = render 'shared/issues' + = render 'shared/issues' - else = render 'shared/empty_states/issues', project_select_button: true diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index af73554086b..6ad76d23df5 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -15,5 +15,4 @@ - if current_user To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. -.prepend-top-default - = render 'shared/merge_requests' += render 'shared/merge_requests' diff --git a/app/views/projects/commit/pipelines.html.haml b/app/views/projects/commit/pipelines.html.haml index 00e7cdd1729..89968cf4e0d 100644 --- a/app/views/projects/commit/pipelines.html.haml +++ b/app/views/projects/commit/pipelines.html.haml @@ -1,6 +1,5 @@ -- page_title "Pipelines", "#{@commit.title} (#{@commit.short_id})", "Commits" +- page_title 'Pipelines', "#{@commit.title} (#{@commit.short_id})", 'Commits' -= render "commit_box" - -= render "ci_menu" -= render "pipelines_list", pipelines: @commit.pipelines.order(id: :desc) += render 'commit_box' += render 'ci_menu' += render 'pipelines_list', pipelines: @pipelines diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 84787155168..ec944d4ffb7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -183,6 +183,8 @@ %li Build traces and artifacts %li LFS objects %li Container registry images + %li CI variables + %li Any encrypted tokens %hr - if can? current_user, :archive_project, @project .row.prepend-top-default diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index bd46af339cf..f3be343daae 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -34,7 +34,7 @@ = note_count .issue-info - #{issue.to_reference} · + #{issuable_reference(issue)} · opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)} - if issue.milestone diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 048417e9b86..a2305f4f547 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -75,7 +75,7 @@ // This element is filled in using JavaScript. .content-block.content-block-small - = render 'new_branch' + = render 'new_branch' unless @issue.confidential? = render 'award_emoji/awards_block', awardable: @issue, inline: true %section.issuable-discussion diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml deleted file mode 100644 index 8d09e2bda11..00000000000 --- a/app/views/projects/labels/destroy.js.haml +++ /dev/null @@ -1,2 +0,0 @@ -- if @labels.empty? - $('.labels').load(document.URL + ' .nothing-here-block').hide().fadeIn(1000) diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 05a8475dcd6..29f861c09c6 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -3,37 +3,35 @@ - hide_class = '' = render "projects/issues/head" -%div{ class: container_class } - .top-area.adjust - .nav-text - Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. +- if @labels.exists? || @prioritized_labels.exists? + %div{ class: container_class } + .top-area.adjust + .nav-text + Labels can be applied to issues and merge requests. Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging. - .nav-controls + .nav-controls + - if can?(current_user, :admin_label, @project) + = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do + New label + + .labels - if can?(current_user, :admin_label, @project) - = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do - New label + -# Only show it in the first page + - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') + .prioritized-labels{ class: ('hide' if hide) } + %h5 Prioritized Labels + %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } + #js-priority-labels-empty-state{ class: "#{'hidden' unless @prioritized_labels.empty?}" } + = render 'shared/empty_states/priority_labels' + - if @prioritized_labels.present? + = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label - .labels - - if can?(current_user, :admin_label, @project) - -# Only show it in the first page - - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') - .prioritized-labels{ class: ('hide' if hide) } - %h5 Prioritized Labels - %ul.content-list.manage-labels-list.js-prioritized-labels{ "data-url" => set_priorities_namespace_project_labels_path(@project.namespace, @project) } - %p.empty-message{ class: ('hidden' unless @prioritized_labels.empty?) } No prioritized labels yet - - if @prioritized_labels.present? - = render partial: 'shared/label', subject: @project, collection: @prioritized_labels, as: :label - - .other-labels - - if can?(current_user, :admin_label, @project) - %h5{ class: ('hide' if hide) } Other Labels - %ul.content-list.manage-labels-list.js-other-labels - - if @labels.present? - = render partial: 'shared/label', subject: @project, collection: @labels, as: :label - = paginate @labels, theme: 'gitlab' - - if @labels.blank? - .nothing-here-block + - if @labels.present? + .other-labels - if can?(current_user, :admin_label, @project) - Create a label or #{link_to 'generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post}. - - else - No labels created + %h5{ class: ('hide' if hide) } Other Labels + %ul.content-list.manage-labels-list.js-other-labels + = render partial: 'shared/label', subject: @project, collection: @labels, as: :label + = paginate @labels, theme: 'gitlab' +- else + = render 'shared/empty_states/labels' diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml index 605c7f61dee..aac74a25b75 100644 --- a/app/views/projects/mattermosts/_no_teams.html.haml +++ b/app/views/projects/mattermosts/_no_teams.html.haml @@ -1,3 +1,7 @@ +- if @teams_error_message + = content_for :flash_message do + .alert.alert-danger= @teams_error_message + %p You aren’t a member of any team on the Mattermost instance at %strong= Gitlab.config.mattermost.host diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index e3b0aa7e644..513f0818169 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -46,7 +46,7 @@ = note_count .merge-request-info - #{merge_request.to_reference} · + #{issuable_reference(merge_request)} · opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)} - if merge_request.target_project.default_branch != merge_request.target_branch diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 65a3a6bddab..54b5ae2402e 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -2,7 +2,7 @@ = f.label :import_url, class: 'control-label' do %span Git repository URL .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true + = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', disabled: true .well.prepend-top-20 %ul @@ -13,4 +13,4 @@ %li The import will time out after 15 minutes. For repositories that take longer, use a clone/push combination. %li - To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. + To migrate an SVN repository, check out #{link_to "this document", help_page_path('workflow/importing/migrating_from_svn')}. diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 26b349e8a62..3a49227961f 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -1,16 +1,7 @@ - if @issues.to_a.any? - - @issues.group_by(&:project).each do |group| - .panel.panel-default.panel-small - - project = group[0] - .panel-heading - = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project) - - if can?(current_user, :create_issue, project) - .pull-right - = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project) - - %ul.content-list.issues-list - - group[1].each do |issue| - = render 'projects/issues/issue', issue: issue + .panel.panel-default.panel-small.panel-without-border + %ul.content-list.issues-list + = render partial: 'projects/issues/issue', collection: @issues = paginate @issues, theme: "gitlab" - else = render 'shared/empty_states/issues' diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml index f11f4471a9d..ead9b84b991 100644 --- a/app/views/shared/_label.html.haml +++ b/app/views/shared/_label.html.haml @@ -36,7 +36,7 @@ %li = link_to 'Edit', edit_label_path(label) %li - = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, remote: true, data: {confirm: 'Remove this label? Are you sure?'} + = link_to 'Delete', destroy_label_path(label), title: 'Delete', method: :delete, data: {confirm: 'Remove this label? Are you sure?'} .pull-right.hidden-xs.hidden-sm.hidden-md = link_to_label(label, subject: subject, type: :merge_request, css_class: 'btn btn-transparent btn-action') do @@ -66,11 +66,15 @@ %a.js-subscribe-button{ data: { url: toggle_subscription_group_label_path(label.group, label) } } Group level + - if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_group, label.project.group) + = link_to promote_namespace_project_label_path(label.project.namespace, label.project, label), title: "Promote to Group Label", class: 'btn btn-transparent btn-action', data: {confirm: "Promoting this label will make this label available to all projects inside this group. Existing project labels with the same name will be merged. Are you sure?", toggle: "tooltip"}, method: :post do + %span.sr-only Promote to Group + = icon('level-up') - if can?(current_user, :admin_label, label) = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do %span.sr-only Edit = icon('pencil-square-o') - = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, remote: true, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do + = link_to destroy_label_path(label), title: "Delete", class: 'btn btn-transparent btn-action remove-row', method: :delete, data: {confirm: label_deletion_confirm_text(label), toggle: "tooltip"} do %span.sr-only Delete = icon('trash-o') diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index 2f3605b4d27..b7982b7fe9b 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -1,16 +1,8 @@ - if @merge_requests.to_a.any? - - @merge_requests.group_by(&:target_project).each do |group| - .panel.panel-default.panel-small - - project = group[0] - .panel-heading - = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project) - - if can?(current_user, :create_merge_request, project) - .pull-right - = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project) + .panel.panel-default.panel-small.panel-without-border + %ul.content-list.mr-list + = render partial: 'projects/merge_requests/merge_request', collection: @merge_requests - %ul.content-list.mr-list - - group[1].each do |merge_request| - = render 'projects/merge_requests/merge_request', merge_request: merge_request = paginate @merge_requests, theme: "gitlab" - else diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml index 0eba1fe075f..c06d1ffa59b 100644 --- a/app/views/shared/_outdated_browser.html.haml +++ b/app/views/shared/_outdated_browser.html.haml @@ -1,8 +1,7 @@ - if outdated_browser? - - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers" .browser-alert GitLab may not work properly because you are using an outdated web browser. %br Please install a - = link_to 'supported web browser', link + = link_to 'supported web browser', help_page_url('install/requirements', anchor: 'supported-web-browsers') for a better experience. diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml new file mode 100644 index 00000000000..ba5c2dae09d --- /dev/null +++ b/app/views/shared/empty_states/_labels.html.haml @@ -0,0 +1,11 @@ +.row.empty-state.labels + .pull-right.col-xs-12.col-sm-6 + .svg-content + = render 'shared/empty_states/icons/labels.svg' + .col-xs-12.col-sm-6 + .text-content + %h4 Labels can be applied to issues and merge requests to categorize them. + %p You can also star label to make it a priority label. + - if can?(current_user, :admin_label, @project) + = link_to 'New label', new_namespace_project_label_path(@project.namespace, @project), class: 'btn btn-new', title: 'New label', id: 'new_label_link' + = link_to 'Generate a default set of labels', generate_namespace_project_labels_path(@project.namespace, @project), method: :post, class: 'btn btn-success btn-inverted', title: 'Generate a default set of labels', id: 'generate_labels_link' diff --git a/app/views/shared/empty_states/_priority_labels.html.haml b/app/views/shared/empty_states/_priority_labels.html.haml new file mode 100644 index 00000000000..bc268301a97 --- /dev/null +++ b/app/views/shared/empty_states/_priority_labels.html.haml @@ -0,0 +1,3 @@ +.text-center + = render 'shared/empty_states/icons/priority_labels.svg' + %p Star labels to start sorting by priority diff --git a/app/views/shared/empty_states/icons/_labels.svg b/app/views/shared/empty_states/icons/_labels.svg new file mode 100644 index 00000000000..dc041a4a78b --- /dev/null +++ b/app/views/shared/empty_states/icons/_labels.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/empty_states/icons/_priority_labels.svg b/app/views/shared/empty_states/icons/_priority_labels.svg new file mode 100644 index 00000000000..7282c2b215e --- /dev/null +++ b/app/views/shared/empty_states/icons/_priority_labels.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/empty_states/_todos_all_done.svg b/app/views/shared/empty_states/icons/_todos_all_done.svg similarity index 100% rename from app/views/shared/empty_states/_todos_all_done.svg rename to app/views/shared/empty_states/icons/_todos_all_done.svg diff --git a/app/views/shared/empty_states/_todos_empty.svg b/app/views/shared/empty_states/icons/_todos_empty.svg similarity index 100% rename from app/views/shared/empty_states/_todos_empty.svg rename to app/views/shared/empty_states/icons/_todos_empty.svg diff --git a/app/views/shared/icons/_icon_action_cancel.svg b/app/views/shared/icons/_icon_action_cancel.svg new file mode 100644 index 00000000000..a1f700eb0ff --- /dev/null +++ b/app/views/shared/icons/_icon_action_cancel.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_action_play.svg b/app/views/shared/icons/_icon_action_play.svg new file mode 100644 index 00000000000..6ac192cd7e9 --- /dev/null +++ b/app/views/shared/icons/_icon_action_play.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_action_retry.svg b/app/views/shared/icons/_icon_action_retry.svg new file mode 100644 index 00000000000..0fa0243f3c0 --- /dev/null +++ b/app/views/shared/icons/_icon_action_retry.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/icons/_icon_action_stop.svg b/app/views/shared/icons/_icon_action_stop.svg new file mode 100644 index 00000000000..1c8e2fe4156 --- /dev/null +++ b/app/views/shared/icons/_icon_action_stop.svg @@ -0,0 +1 @@ + diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index b5c0a7fd6d4..a736bfd91e2 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: help_page_path('workflow/notifications'), 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/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml new file mode 100644 index 00000000000..eda872049fd --- /dev/null +++ b/changelogs/unreleased/20852-getting-started-project-better-blank-state-for-labels-view.yml @@ -0,0 +1,4 @@ +--- +title: Added labels empty state +merge_request: 7443 +author: diff --git a/changelogs/unreleased/23634-remove-project-grouping.yml b/changelogs/unreleased/23634-remove-project-grouping.yml new file mode 100644 index 00000000000..dde8b2d1815 --- /dev/null +++ b/changelogs/unreleased/23634-remove-project-grouping.yml @@ -0,0 +1,4 @@ +--- +title: Don't group issues by project on group-level and dashboard issue indexes. +merge_request: 8111 +author: Bernardo Castro diff --git a/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml new file mode 100644 index 00000000000..587ef4f9a73 --- /dev/null +++ b/changelogs/unreleased/23767-disable-storing-of-sensitive-information.yml @@ -0,0 +1,4 @@ +--- +title: Fix disable storing of sensitive information when importing a new repo +merge_request: 8885 +author: Bernard Pietraga diff --git a/changelogs/unreleased/24795_refactor_merge_request_build_service.yml b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml new file mode 100644 index 00000000000..b735fb57649 --- /dev/null +++ b/changelogs/unreleased/24795_refactor_merge_request_build_service.yml @@ -0,0 +1,4 @@ +--- +title: Refactor MergeRequests::BuildService +merge_request: 8462 +author: Rydkin Maxim diff --git a/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml new file mode 100644 index 00000000000..50a5c879446 --- /dev/null +++ b/changelogs/unreleased/25360-remove-flash-warning-from-login-page.yml @@ -0,0 +1,4 @@ +--- +title: Remove flash warning from login page +merge_request: 8864 +author: Gerald J. Padilla diff --git a/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml new file mode 100644 index 00000000000..9506692dd40 --- /dev/null +++ b/changelogs/unreleased/25910-convert-manual-action-icons-to-svg-to-propperly-position-them.yml @@ -0,0 +1,4 @@ +--- +title: Convert pipeline action icons to svg to have them propperly positioned +merge_request: +author: diff --git a/changelogs/unreleased/26852-fix-slug-for-openshift.yml b/changelogs/unreleased/26852-fix-slug-for-openshift.yml new file mode 100644 index 00000000000..fb65b068b23 --- /dev/null +++ b/changelogs/unreleased/26852-fix-slug-for-openshift.yml @@ -0,0 +1,4 @@ +--- +title: Avoid repeated dashes in $CI_ENVIRONMENT_SLUG +merge_request: 8638 +author: diff --git a/changelogs/unreleased/27488-fix-jwt-version.yml b/changelogs/unreleased/27488-fix-jwt-version.yml new file mode 100644 index 00000000000..5135ff0fd60 --- /dev/null +++ b/changelogs/unreleased/27488-fix-jwt-version.yml @@ -0,0 +1,4 @@ +--- +title: Update and pin the `jwt` gem to ~> 1.5.6 +merge_request: +author: diff --git a/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml new file mode 100644 index 00000000000..11d1f55172b --- /dev/null +++ b/changelogs/unreleased/395-fix-notification-when-group-set-to-watch.yml @@ -0,0 +1,4 @@ +--- +title: Fix notifications when set at group level +merge_request: 6813 +author: Alexandre Maia diff --git a/changelogs/unreleased/cop-gem-fetcher.yml b/changelogs/unreleased/cop-gem-fetcher.yml new file mode 100644 index 00000000000..506815a5b54 --- /dev/null +++ b/changelogs/unreleased/cop-gem-fetcher.yml @@ -0,0 +1,4 @@ +--- +title: Cop for gem fetched from a git source +merge_request: 8856 +author: Adam Pahlevi diff --git a/changelogs/unreleased/document-how-to-vue.yml b/changelogs/unreleased/document-how-to-vue.yml new file mode 100644 index 00000000000..863e41b6413 --- /dev/null +++ b/changelogs/unreleased/document-how-to-vue.yml @@ -0,0 +1,4 @@ +--- +title: Adds documentation for how to use Vue.js +merge_request: 8866 +author: diff --git a/changelogs/unreleased/dz-nested-groups-improvements-2.yml b/changelogs/unreleased/dz-nested-groups-improvements-2.yml new file mode 100644 index 00000000000..8e4eb7f1fff --- /dev/null +++ b/changelogs/unreleased/dz-nested-groups-improvements-2.yml @@ -0,0 +1,4 @@ +--- +title: Add read-only full_path and full_name attributes to Group API +merge_request: 8827 +author: diff --git a/changelogs/unreleased/fix-27479.yml b/changelogs/unreleased/fix-27479.yml new file mode 100644 index 00000000000..cc72a830695 --- /dev/null +++ b/changelogs/unreleased/fix-27479.yml @@ -0,0 +1,4 @@ +--- +title: Remove new branch button for confidential issues +merge_request: +author: diff --git a/changelogs/unreleased/fix-ci-build-policy.yml b/changelogs/unreleased/fix-ci-build-policy.yml new file mode 100644 index 00000000000..26003713ed4 --- /dev/null +++ b/changelogs/unreleased/fix-ci-build-policy.yml @@ -0,0 +1,4 @@ +--- +title: Improve build policy and access abilities +merge_request: 8711 +author: diff --git a/changelogs/unreleased/fix-import-encrypt-atts.yml b/changelogs/unreleased/fix-import-encrypt-atts.yml new file mode 100644 index 00000000000..e34d895570b --- /dev/null +++ b/changelogs/unreleased/fix-import-encrypt-atts.yml @@ -0,0 +1,4 @@ +--- +title: Ignore encrypted attributes in Import/Export +merge_request: +author: diff --git a/changelogs/unreleased/hardcode-title-system-note.yml b/changelogs/unreleased/hardcode-title-system-note.yml new file mode 100644 index 00000000000..1b0a63efa51 --- /dev/null +++ b/changelogs/unreleased/hardcode-title-system-note.yml @@ -0,0 +1,4 @@ +--- +title: Ensure autogenerated title does not cause failing spec +merge_request: 8963 +author: brian m. carlson diff --git a/changelogs/unreleased/improve-ci-example-php-doc.yml b/changelogs/unreleased/improve-ci-example-php-doc.yml new file mode 100644 index 00000000000..39a85e3d261 --- /dev/null +++ b/changelogs/unreleased/improve-ci-example-php-doc.yml @@ -0,0 +1,4 @@ +--- +title: Changed composer installer script in the CI PHP example doc +merge_request: 4342 +author: Jeffrey Cafferata diff --git a/changelogs/unreleased/issue-sidebar-empty-assignee.yml b/changelogs/unreleased/issue-sidebar-empty-assignee.yml new file mode 100644 index 00000000000..263af75b9e9 --- /dev/null +++ b/changelogs/unreleased/issue-sidebar-empty-assignee.yml @@ -0,0 +1,4 @@ +--- +title: Resets assignee dropdown when sidebar is open +merge_request: +author: diff --git a/changelogs/unreleased/label-promotion.yml b/changelogs/unreleased/label-promotion.yml new file mode 100644 index 00000000000..2ab997bf420 --- /dev/null +++ b/changelogs/unreleased/label-promotion.yml @@ -0,0 +1,4 @@ +--- +title: "Project labels can now be promoted to group labels" +merge_request: 7242 +author: Olaf Tomalka diff --git a/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml new file mode 100644 index 00000000000..e69fcd2aa63 --- /dev/null +++ b/changelogs/unreleased/sh-add-project-id-index-project-authorizations.yml @@ -0,0 +1,4 @@ +--- +title: Add project ID index to `project_authorizations` table to optimize queries +merge_request: +author: diff --git a/changelogs/unreleased/zj-slow-service-fetch.yml b/changelogs/unreleased/zj-slow-service-fetch.yml new file mode 100644 index 00000000000..8037361d2fc --- /dev/null +++ b/changelogs/unreleased/zj-slow-service-fetch.yml @@ -0,0 +1,4 @@ +--- +title: Improve performance of slash commands +merge_request: 8876 +author: diff --git a/config/routes/project.rb b/config/routes/project.rb index 6620b765e02..f36febc6e04 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -220,6 +220,7 @@ constraints(ProjectUrlConstrainer.new) do end member do + post :promote post :toggle_subscription delete :remove_priority end diff --git a/db/migrate/20161207231626_add_environment_slug.rb b/db/migrate/20161207231626_add_environment_slug.rb index 7153e6a32b1..8e98ee5b9ba 100644 --- a/db/migrate/20161207231626_add_environment_slug.rb +++ b/db/migrate/20161207231626_add_environment_slug.rb @@ -8,8 +8,9 @@ class AddEnvironmentSlug < ActiveRecord::Migration DOWNTIME_REASON = 'Adding NOT NULL column environments.slug with dependent data' # Used to generate random suffixes for the slug + LETTERS = 'a'..'z' NUMBERS = '0'..'9' - SUFFIX_CHARS = ('a'..'z').to_a + NUMBERS.to_a + SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a def up environments = Arel::Table.new(:environments) @@ -39,17 +40,24 @@ class AddEnvironmentSlug < ActiveRecord::Migration slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-') # Must start with a letter - slugified = "env-" + slugified if NUMBERS.cover?(slugified[0]) + slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0]) + + # Repeated dashes are invalid (OpenShift limitation) + slugified.gsub!(/\-+/, '-') # Maximum length: 24 characters (OpenShift limitation) slugified = slugified[0..23] - # Cannot end with a "-" character (Kubernetes label limitation) - slugified = slugified[0..-2] if slugified[-1] == "-" + # Cannot end with a dash (Kubernetes label limitation) + slugified.chop! if slugified.end_with?('-') # Add a random suffix, shortening the current string if necessary, if it # has been slugified. This ensures uniqueness. - slugified = slugified[0..16] + "-" + random_suffix if slugified != name + if slugified != name + slugified = slugified[0..16] + slugified << '-' unless slugified.end_with?('-') + slugified << random_suffix + end slugified end diff --git a/db/migrate/20170130204620_add_index_to_project_authorizations.rb b/db/migrate/20170130204620_add_index_to_project_authorizations.rb new file mode 100644 index 00000000000..e9a0aee4d6a --- /dev/null +++ b/db/migrate/20170130204620_add_index_to_project_authorizations.rb @@ -0,0 +1,11 @@ +class AddIndexToProjectAuthorizations < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index(:project_authorizations, :project_id) + end +end diff --git a/db/schema.rb b/db/schema.rb index 3c836db27fc..5efb4f6595c 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: 20170121130655) do +ActiveRecord::Schema.define(version: 20170130204620) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -874,6 +874,7 @@ ActiveRecord::Schema.define(version: 20170121130655) do t.integer "access_level" end + add_index "project_authorizations", ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree add_index "project_authorizations", ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree create_table "project_features", force: :cascade do |t| diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index d7cfb464f74..a6300e18dc0 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -379,6 +379,10 @@ Read more about the individual driver's config options in the filesystem. Remember to enable backups with your object storage provider if desired. +> **Important** Enabling storage driver other than `filesystem` would mean +that your Docker client needs to be able to access the storage backend directly. +So you must use an address that resolves and is accessible outside GitLab server. + --- **Omnibus GitLab installations** diff --git a/doc/api/groups.md b/doc/api/groups.md index f7807390e68..3b38e3e1bee 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -25,7 +25,14 @@ GET /groups "id": 1, "name": "Foobar Group", "path": "foo-bar", - "description": "An interesting group" + "description": "An interesting group", + "visibility_level": 20, + "lfs_enabled": true, + "avatar_url": "http://localhost:3000/uploads/group/avatar/1/foo.jpg", + "web_url": "http://localhost:3000/groups/foo-bar", + "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar" } ] ``` @@ -149,6 +156,8 @@ Example response: "avatar_url": null, "web_url": "https://gitlab.example.com/groups/twitter", "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar", "projects": [ { "id": 7, @@ -372,6 +381,8 @@ Example response: "avatar_url": null, "web_url": "http://gitlab.example.com/groups/h5bp", "request_access_enabled": false, + "full_name": "Foobar Group", + "full_path": "foo-bar", "projects": [ { "id": 9, diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md index 630207ffa09..c4c4d95b68a 100644 --- a/doc/ci/autodeploy/index.md +++ b/doc/ci/autodeploy/index.md @@ -1,6 +1,6 @@ # Auto deploy -> [Introduced][mr-8135] in GitLab 8.15. +> [Introduced][mr-8135] in GitLab 8.15. Currently requires a [Public project][project-settings]. 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` @@ -33,6 +33,7 @@ enable [Kubernetes service][kubernetes-service]. created automatically for you. [mr-8135]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8135 +[project-settings]: https://docs.gitlab.com/ce/public_access/public_access.html [project-services]: ../../project_services/project_services.md [auto-deploy-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml/tree/master/autodeploy [kubernetes-service]: ../../project_services/kubernetes.md diff --git a/doc/ci/environments.md b/doc/ci/environments.md index 98cd29c9567..ef04c537367 100644 --- a/doc/ci/environments.md +++ b/doc/ci/environments.md @@ -297,7 +297,7 @@ deploy_review: - echo "Deploy a review app" environment: name: review/$CI_BUILD_REF_NAME - url: https://$CI_BUILD_REF_SLUG.review.example.com + url: https://$CI_ENVIRONMENT_SLUG.example.com only: - branches except: @@ -318,15 +318,15 @@ also contain `/`, or other characters that would be invalid in a domain name or URL, we use `$CI_ENVIRONMENT_SLUG` in the `environment:url` so that the environment can get a specific and distinct URL for each branch. In this case, given a `$CI_BUILD_REF_NAME` of `100-Do-The-Thing`, the URL will be something -like `https://review-100-do-the-4f99a2.example.com`. Again, the way you set up +like `https://100-do-the-4f99a2.example.com`. Again, the way you set up the web server to serve these requests is based on your setup. You could also use `$CI_BUILD_REF_SLUG` in `environment:url`, e.g.: -`https://$CI_BUILD_REF_SLUG.review.example.com`. We use `$CI_ENVIRONMENT_SLUG` +`https://$CI_BUILD_REF_SLUG.example.com`. We use `$CI_ENVIRONMENT_SLUG` here because it is guaranteed to be unique, but if you're using a workflow like [GitLab Flow][gitlab-flow], collisions are very unlikely, and you may prefer environment names to be more closely based on the branch name - the example -above would give you an URL like `https://100-do-the-thing.review.example.com` +above would give you an URL like `https://100-do-the-thing.example.com` Last but not least, we tell the job to run [`only`][only] on branches [`except`][only] master. diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 82ffb841729..5eeec92d976 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -235,7 +235,11 @@ cache: before_script: # Install composer dependencies -- curl --silent --show-error https://getcomposer.org/installer | php +- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig +- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" +- php composer-setup.php +- php -r "unlink('composer-setup.php'); unlink('installer.sig');" - php composer.phar install ... diff --git a/doc/development/frontend.md b/doc/development/frontend.md index f79bd23dc90..75fdf3d8e63 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -23,6 +23,69 @@ some ideas with React.js as well as Angular. To get started with Vue, read through [their documentation][vue-docs]. +#### How to build a new feature with Vue.js +**Components, Stores and Services** + +In some features implemented with Vue.js, like the [issue board][issue-boards] +or [environments table][environments-table] +you can find a clear separation of concerns: + +``` +new_feature +├── components +│ └── component.js.es6 +│ └── ... +├── store +│ └── new_feature_store.js.es6 +├── service +│ └── new_feature_service.js.es6 +├── new_feature_bundle.js.es6 +``` +_For consistency purposes, we recommend you to follow the same structure._ + +Let's look into each of them: + +**A `*_bundle.js` file** + +This is the index file of your new feature. This is where the root Vue instance +of the new feature should be. + +Don't forget to follow [these steps.][page-specific-javascript] + +**A folder for Components** + +This folder holds all components that are specific of this new feature. +If you need to use or create a component that will probably be used somewhere +else, please refer to `vue_shared/components`. + +A good thumb rule to know when you should create a component is to think if +it will be reusable elsewhere. + +For example, tables are used in a quite amount of places across GitLab, a table +would be a good fit for a component. +On the other hand, a table cell used only in on table, would not be a good use +of this pattern. + +You can read more about components in Vue.js site, [Component System][component-system] + +**A folder for the Store** + +The Store is a simple object that allows us to manage the state in a single +source of truth. + +The concept we are trying to follow is better explained by Vue documentation +itself, please read this guide: [State Management][state-management] + +**A folder for the Service** + +The Service is used only to communicate with the server. +It does not store or manipulate any data. +We use [vue-resource][vue-resource-repo] to +communicate with the server. + +The [issue boards service][issue-boards-service] +is a good example of this pattern. + ## Performance ### Resources @@ -198,8 +261,8 @@ As long as the fixtures don't change, `rake teaspoon:tests` is sufficient If you need to debug your tests and/or application code while they're running, navigate to [localhost:3000/teaspoon](http://localhost:3000/teaspoon) -in your browser, open DevTools, and run tests for individual files by clicking -on them. This is also much faster than setting up and running tests from the +in your browser, open DevTools, and run tests for individual files by clicking +on them. This is also much faster than setting up and running tests from the command line. Please note: Not all of the frontend fixtures are generated. Some are still static @@ -294,20 +357,27 @@ For our currently-supported browsers, see our [requirements][requirements]. [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md [requirements]: ../install/requirements.md#supported-web-browsers +[issue-boards]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/boards +[environments-table]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/assets/javascripts/environments +[page_specific_javascript]: https://docs.gitlab.com/ce/development/frontend.html#page-specific-javascript +[component-system]: https://vuejs.org/v2/guide/#Composing-with-Components +[state-management]: https://vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch +[vue-resource-repo]: https://github.com/pagekit/vue-resource +[issue-boards-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/boards/services/board_service.js.es6 ## Gotchas ### Spec errors due to use of ES6 features in `.js` files -If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being -thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually, -you may have included `ES6`-style JavaScript in files that don't have the -`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file -you're working in (`git mv `). +If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being +thrown in Teaspoon, Spinach, or Rspec tests but can't reproduce them manually, +you may have included `ES6`-style JavaScript in files that don't have the +`.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file +you're working in (`git mv `). ### Spec errors due to use of unsupported JavaScript -Similar errors will be thrown if you're using JavaScript features not yet +Similar errors will be thrown if you're using JavaScript features not yet supported by our test runner's version of webkit, whether or not you've updated the file extension. Examples of unsupported JavaScript features are: @@ -322,20 +392,20 @@ the file extension. Examples of unsupported JavaScript features are: - Symbol/Symbol.iterator - Spread -Until these are polyfilled or transpiled appropriately, they should not be used. -Please update this list with additional unsupported features or when any of +Until these are polyfilled or transpiled appropriately, they should not be used. +Please update this list with additional unsupported features or when any of these are made usable. ### Spec errors due to JavaScript not enabled -If, as a result of a change you've made, a feature now depends on JavaScript to +If, as a result of a change you've made, a feature now depends on JavaScript to run correctly, you need to make sure a JavaScript web driver is enabled when -specs are run. If you don't you'll see vague error messages from the spec -runner, and an explosion of vague console errors in the HTML snapshot. +specs are run. If you don't you'll see vague error messages from the spec +runner, and an explosion of vague console errors in the HTML snapshot. -To enable a JavaScript driver in an `rspec` test, add `js: true` to the -individual spec or the context block containing multiple specs that need -JavaScript enabled: +To enable a JavaScript driver in an `rspec` test, add `js: true` to the +individual spec or the context block containing multiple specs that need +JavaScript enabled: ```ruby @@ -354,8 +424,8 @@ describe "Admin::AbuseReports", js: true do end ``` -In Spinach, the JavaScript driver is enabled differently. In the `*.feature` -file for the failing spec, add the `@javascript` flag above the Scenario: +In Spinach, the JavaScript driver is enabled differently. In the `*.feature` +file for the failing spec, add the `@javascript` flag above the Scenario: ``` @javascript diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 706bb180912..1b19587a0b8 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -109,7 +109,7 @@ Dropdowns are used to allow users to choose one (or many) options from a list of ### Max size -The max height for dropdowns should target **10-15 items**. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height. +The max height for dropdowns should target **10-15** single line items, or **7-10** multi-line items. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height. --- diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md index 31cc9dd2a53..5b65d531e54 100644 --- a/doc/development/ux_guide/copy.md +++ b/doc/development/ux_guide/copy.md @@ -102,6 +102,12 @@ When using the Alt keystrokes in Windows, use the numeric keypad, not ## Terminology Only use the terms in the tables below. +### Projects and Groups + +| Term | Use | :no_entry_sign: Don't | +| ---- | --- | ----- | +| Members | When discussing the people who are a part of a project or a group. | Don't use `users`. | + ### Issues #### Adjectives (states) @@ -117,7 +123,7 @@ Use `5 open issues` and don’t use `5 pending issues`. #### Verbs (actions) -| Term | Use | Don’t | +| Term | Use | :no_entry_sign: Don’t | | ---- | --- | --- | | Add | Add an issue | Don’t use `create` or `new` | | View | View an open or closed issue || @@ -158,7 +164,7 @@ The form should be titled `Edit issue`. The submit button should be labeled `Sav #### Verbs (actions) -| Term | Use | Don’t | +| Term | Use | :no_entry_sign: Don’t | | ---- | --- | --- | | Add | Add a merge request | Do not use `create` or `new` | | View | View an open or merged merge request || diff --git a/doc/project_services/slack.md b/doc/project_services/slack.md index 0b682b43810..eaceb2be137 100644 --- a/doc/project_services/slack.md +++ b/doc/project_services/slack.md @@ -15,7 +15,7 @@ Slack: After you set up Slack, it's time to set up GitLab. -Go to your project's **Settings > Services > Slack Notifications** and you will see a +Go to your project's **Settings > Integrations > Slack Notifications** and you will see a checkbox with the following events that can be triggered: - Push diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index dfc762fe1d3..cb1c1a84f8c 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -22,7 +22,8 @@ with all their related data and be moved into a new GitLab instance. | GitLab version | Import/Export version | | -------- | -------- | -| 8.13.0 to current | 0.1.5 | +| 8.16.2 to current | 0.1.6 | +| 8.13.0 | 0.1.5 | | 8.12.0 | 0.1.4 | | 8.10.3 | 0.1.3 | | 8.10.0 | 0.1.2 | @@ -47,6 +48,9 @@ The following items will NOT be exported: - Build traces and artifacts - LFS objects +- Container registry images +- CI variables +- Any encrypted tokens ## Exporting a project and its data diff --git a/features/steps/dashboard/todos.rb b/features/steps/dashboard/todos.rb index 344b6fda9a6..2bbc43b491f 100644 --- a/features/steps/dashboard/todos.rb +++ b/features/steps/dashboard/todos.rb @@ -25,15 +25,18 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end step 'I should see todos assigned to me' do + merge_request_reference = merge_request.to_reference(full: true) + issue_reference = issue.to_reference(full: true) + page.within('.todos-pending-count') { expect(page).to have_content '4' } expect(page).to have_content 'To do 4' expect(page).to have_content 'Done 0' expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title) - should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?") - should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title) - should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title) + should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title) + should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?") + should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title) + should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title) end step 'I mark the todo as done' do @@ -44,10 +47,13 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps page.within('.todos-pending-count') { expect(page).to have_content '3' } expect(page).to have_content 'To do 3' expect(page).to have_content 'Done 1' - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" + should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}" end step 'I mark all todos as done' do + merge_request_reference = merge_request.to_reference(full: true) + issue_reference = issue.to_reference(full: true) + click_link 'Mark all as done' page.within('.todos-pending-count') { expect(page).to have_content '0' } @@ -55,27 +61,30 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps expect(page).to have_content 'Done 4' expect(page).to have_content "You're all done!" expect('.prepend-top-default').not_to have_link project.name_with_namespace - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" - should_not_see_todo "John Doe mentioned you on issue #{issue.to_reference}" - should_not_see_todo "John Doe assigned you issue #{issue.to_reference}" - should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}" + should_not_see_todo "John Doe assigned you merge request #{merge_request_reference}" + should_not_see_todo "John Doe mentioned you on issue #{issue_reference}" + should_not_see_todo "John Doe assigned you issue #{issue_reference}" + should_not_see_todo "Mary Jane mentioned you on issue #{issue_reference}" end step 'I should see the todo marked as done' do click_link 'Done 1' expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false) + should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference(full: true)}", merge_request.title, false) end step 'I should see all todos marked as done' do + merge_request_reference = merge_request.to_reference(full: true) + issue_reference = issue.to_reference(full: true) + click_link 'Done 4' expect(page).to have_link project.name_with_namespace - should_see_todo(1, "John Doe assigned you merge request #{merge_request.to_reference}", merge_request.title, false) - should_see_todo(2, "John Doe mentioned you on issue #{issue.to_reference}", "#{current_user.to_reference} Wdyt?", false) - should_see_todo(3, "John Doe assigned you issue #{issue.to_reference}", issue.title, false) - should_see_todo(4, "Mary Jane mentioned you on issue #{issue.to_reference}", issue.title, false) + should_see_todo(1, "John Doe assigned you merge request #{merge_request_reference}", merge_request.title, false) + should_see_todo(2, "John Doe mentioned you on issue #{issue_reference}", "#{current_user.to_reference} Wdyt?", false) + should_see_todo(3, "John Doe assigned you issue #{issue_reference}", issue.title, false) + should_see_todo(4, "Mary Jane mentioned you on issue #{issue_reference}", issue.title, false) end step 'I filter by "Enterprise"' do @@ -111,16 +120,16 @@ class Spinach::Features::DashboardTodos < Spinach::FeatureSteps end step 'I should not see todos related to "Mary Jane" in the list' do - should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference}" + should_not_see_todo "Mary Jane mentioned you on issue #{issue.to_reference(full: true)}" end step 'I should not see todos related to "Merge Requests" in the list' do - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" + should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}" end step 'I should not see todos related to "Assignments" in the list' do - should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference}" - should_not_see_todo "John Doe assigned you issue #{issue.to_reference}" + should_not_see_todo "John Doe assigned you merge request #{merge_request.to_reference(full: true)}" + should_not_see_todo "John Doe assigned you issue #{issue.to_reference(full: true)}" end step 'I click on the todo' do diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index f74a9b5df47..4a35b71af2f 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -15,17 +15,16 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps step 'I delete all labels' do page.within '.labels' do - page.all('.remove-row').each do |remove| - remove.click - sleep 0.05 + page.all('.remove-row').each do + first('.remove-row').click end end end step 'I should see labels help message' do page.within '.labels' do - expect(page).to have_content 'Create a label or generate a default set '\ - 'of labels' + expect(page).to have_content 'Generate a default set of labels' + expect(page).to have_content 'New label' end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 9f59939e9ae..a07b2a9ca0f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -137,6 +137,7 @@ module API expose :avatar_url expose :web_url expose :request_access_enabled + expose :full_name, :full_path expose :statistics, if: :statistics do with_options format_with: -> (value) { value.to_i } do diff --git a/lib/api/services.rb b/lib/api/services.rb index a0abec49438..1456fe4688b 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -661,6 +661,14 @@ module API end trigger_services.each do |service_slug, settings| + helpers do + def chat_command_service(project, service_slug, params) + project.services.active.where(template: false).find do |service| + service.try(:token) == params[:token] && service.to_param == service_slug.underscore + end + end + end + params do requires :id, type: String, desc: 'The ID of a project' end @@ -679,9 +687,8 @@ module API # This is not accurate, but done to prevent leakage of the project names not_found!('Service') unless project - service = project.find_or_initialize_service(service_slug.underscore) - - result = service.try(:active?) && service.try(:trigger, params) + service = chat_command_service(project, service_slug, params) + result = service.try(:trigger, params) if result status result[:status] || 200 diff --git a/lib/gitlab/ci/status/build/cancelable.rb b/lib/gitlab/ci/status/build/cancelable.rb index a979fe7d573..67bbc3c4849 100644 --- a/lib/gitlab/ci/status/build/cancelable.rb +++ b/lib/gitlab/ci/status/build/cancelable.rb @@ -10,7 +10,7 @@ module Gitlab end def action_icon - 'ban' + 'icon_action_cancel' end def action_path diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 1bf949c96dd..0f4b7b24cef 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -26,17 +26,13 @@ module Gitlab end def action_icon - 'play' + 'icon_action_play' end def action_title 'Play' end - def action_class - 'ci-play-icon' - end - def action_path play_namespace_project_build_path(subject.project.namespace, subject.project, diff --git a/lib/gitlab/ci/status/build/retryable.rb b/lib/gitlab/ci/status/build/retryable.rb index 8e38d6a8523..6b362af7634 100644 --- a/lib/gitlab/ci/status/build/retryable.rb +++ b/lib/gitlab/ci/status/build/retryable.rb @@ -10,7 +10,7 @@ module Gitlab end def action_icon - 'refresh' + 'icon_action_retry' end def action_title diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index e1dfdb76d41..90401cad0d2 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -26,7 +26,7 @@ module Gitlab end def action_icon - 'stop' + 'icon_action_stop' end def action_title diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 73b6ab5a635..3dd2b9e01f6 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -42,9 +42,6 @@ module Gitlab raise NotImplementedError end - def action_class - end - def action_path raise NotImplementedError end diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb index 74bbcdcb3dd..559e3939da6 100644 --- a/lib/gitlab/cycle_analytics/base_stage.rb +++ b/lib/gitlab/cycle_analytics/base_stage.rb @@ -13,7 +13,7 @@ module Gitlab end def as_json - AnalyticsStageSerializer.new.represent(self).as_json + AnalyticsStageSerializer.new.represent(self) end def title diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb index 5245b9ca8fc..d5bf6149749 100644 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb @@ -18,7 +18,7 @@ module Gitlab private def serialize(event) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json + AnalyticsMergeRequestSerializer.new(project: @project).represent(event) end end end diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb index 0d8da99455e..3df9cbdcfce 100644 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb @@ -16,7 +16,7 @@ module Gitlab private def serialize(event) - AnalyticsIssueSerializer.new(project: @project).represent(event).as_json + AnalyticsIssueSerializer.new(project: @project).represent(event) end end end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index 88a8710dbe6..7d342a2d2cb 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -37,7 +37,7 @@ module Gitlab def serialize_commit(event, st_commit, query) commit = Commit.new(Gitlab::Git::Commit.new(st_commit), @project) - AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit).as_json + AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit) end end end diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb index 4df0bd06393..4c7b3f4467f 100644 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb @@ -15,7 +15,7 @@ module Gitlab end def serialize(event) - AnalyticsMergeRequestSerializer.new(project: @project).represent(event).as_json + AnalyticsMergeRequestSerializer.new(project: @project).represent(event) end end end diff --git a/lib/gitlab/cycle_analytics/stage_summary.rb b/lib/gitlab/cycle_analytics/stage_summary.rb index b34baf5b081..fc77bd86097 100644 --- a/lib/gitlab/cycle_analytics/stage_summary.rb +++ b/lib/gitlab/cycle_analytics/stage_summary.rb @@ -16,7 +16,7 @@ module Gitlab private def serialize(summary_object) - AnalyticsSummarySerializer.new.represent(summary_object).as_json + AnalyticsSummarySerializer.new.represent(summary_object) end end end diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb index a34731a5fcd..36c0260dbfe 100644 --- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb @@ -23,7 +23,7 @@ module Gitlab private def serialize(event) - AnalyticsBuildSerializer.new.represent(event['build']).as_json + AnalyticsBuildSerializer.new.represent(event['build']) end end end diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index eb667a85b78..d679edec36b 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,7 +3,7 @@ module Gitlab extend self # For every version update, the version history in import_export.md has to be kept up to date. - VERSION = '0.1.5' + VERSION = '0.1.6' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 08ad3274b38..416194e57d7 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -39,7 +39,6 @@ project_tree: - :author - :events - :statuses - - :variables - :triggers - :deploy_keys - :services diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 19e43cce768..0319d7707a8 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -4,7 +4,6 @@ module Gitlab OVERRIDES = { snippets: :project_snippets, pipelines: 'Ci::Pipeline', statuses: 'commit_status', - variables: 'Ci::Variable', triggers: 'Ci::Trigger', builds: 'Ci::Build', hooks: 'ProjectHook', @@ -24,6 +23,8 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels project_label project_labels group_label group_labels].freeze + TOKEN_RESET_MODELS = %w[Ci::Trigger Ci::Build ProjectHook].freeze + def self.create(*args) new(*args).create end @@ -61,7 +62,9 @@ module Gitlab update_project_references handle_group_label if group_label? - reset_ci_tokens if @relation_name == 'Ci::Trigger' + reset_tokens! + remove_encrypted_attributes! + @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] set_st_diffs if @relation_name == :merge_request_diff end @@ -140,11 +143,22 @@ module Gitlab end end - def reset_ci_tokens - return unless Gitlab::ImportExport.reset_tokens? + def reset_tokens! + return unless Gitlab::ImportExport.reset_tokens? && TOKEN_RESET_MODELS.include?(@relation_name.to_s) # If we import/export a project to the same instance, tokens will have to be reset. - @relation_hash['token'] = nil + # We also have to reset them to avoid issues when the gitlab secrets file cannot be copied across. + relation_class.attribute_names.select { |name| name.include?('token') }.each do |token| + @relation_hash[token] = nil + end + end + + def remove_encrypted_attributes! + return unless relation_class.respond_to?(:encrypted_attributes) && relation_class.encrypted_attributes.any? + + relation_class.encrypted_attributes.each_key do |key| + @relation_hash[key.to_s] = nil + end end def relation_class diff --git a/rubocop/cop/gem_fetcher.rb b/rubocop/cop/gem_fetcher.rb new file mode 100644 index 00000000000..4a63c760744 --- /dev/null +++ b/rubocop/cop/gem_fetcher.rb @@ -0,0 +1,28 @@ +module RuboCop + module Cop + # Cop that checks for all gems specified in the Gemfile, and will + # alert if any gem is to be fetched not from the RubyGems index. + # This enforcement is done so as to minimize external build + # dependencies and build times. + class GemFetcher < RuboCop::Cop::Cop + MSG = 'Do not use gems from git repositories, only use gems from RubyGems.' + + GIT_KEYS = [:git, :github] + + def on_send(node) + file_path = node.location.expression.source_buffer.name + return unless file_path.end_with?("Gemfile") + + func_name = node.children[1] + return unless func_name == :gem + + node.children.last.each_node(:pair) do |pair| + key_name = pair.children[0].children[0].to_sym + if GIT_KEYS.include?(key_name) + add_offense(node, :selector) + end + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 7922e19768b..7f20754ee51 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,3 +1,4 @@ require_relative 'migration_helpers' require_relative 'cop/migration/add_index' require_relative 'cop/migration/column_with_default' +require_relative 'cop/gem_fetcher' diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index a95cfc5c6be..ebd2d0e092b 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -4,7 +4,6 @@ describe Projects::CommitController do let(:project) { create(:project, :repository) } let(:user) { create(:user) } let(:commit) { project.commit("master") } - let(:pipeline) { create(:ci_pipeline, project: project, commit: commit) } let(:master_pickable_sha) { '7d3b0f7cff5f37573aea97cebfd5692ea1689924' } let(:master_pickable_commit) { project.commit(master_pickable_sha) } @@ -322,11 +321,26 @@ describe Projects::CommitController do end context 'when the commit exists' do - context 'when the commit has one or more pipelines' do - it 'shows pipelines' do - get_pipelines(id: commit.id) + context 'when the commit has pipelines' do + before do + create(:ci_pipeline, project: project, sha: commit.id) + end - expect(response).to be_ok + context 'when rendering a HTML format' do + it 'shows pipelines' do + get_pipelines(id: commit.id) + + expect(response).to be_ok + end + end + + context 'when rendering a JSON format' do + it 'responds with serialized pipelines' do + get_pipelines(id: commit.id, format: :json) + + expect(response).to be_ok + expect(JSON.parse(response.body)).not_to be_empty + end end end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index ec6cea5c0f4..3e0326dd47d 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -112,4 +112,49 @@ describe Projects::LabelsController do post :toggle_subscription, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label.to_param end end + + describe 'POST #promote' do + let!(:promoted_label_name) { "Promoted Label" } + let!(:label_1) { create(:label, title: promoted_label_name, project: project) } + + context 'not group owner' do + it 'denies access' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(response).to have_http_status(404) + end + end + + context 'group owner' do + before do + GroupMember.add_users_to_group(group, [user], :owner) + end + + it 'gives access' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(response).to redirect_to(namespace_project_labels_path) + end + + it 'promotes the label' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + + expect(Label.where(id: label_1.id)).to be_empty + expect(GroupLabel.find_by(title: promoted_label_name)).not_to be_nil + end + + context 'service raising InvalidRecord' do + before do + expect_any_instance_of(Labels::PromoteService).to receive(:execute) do |label| + raise ActiveRecord::RecordInvalid.new(label_1) + end + end + + it 'returns to label list' do + post :promote, namespace_id: project.namespace.to_param, project_id: project.to_param, id: label_1.to_param + expect(response).to redirect_to(namespace_project_labels_path) + end + end + end + end end diff --git a/spec/controllers/projects/mattermosts_controller_spec.rb b/spec/controllers/projects/mattermosts_controller_spec.rb index 2ae635a1244..cae733f0cfb 100644 --- a/spec/controllers/projects/mattermosts_controller_spec.rb +++ b/spec/controllers/projects/mattermosts_controller_spec.rb @@ -13,13 +13,13 @@ describe Projects::MattermostsController do before do allow_any_instance_of(MattermostSlashCommandsService). to receive(:list_teams).and_return([]) - - get(:new, - namespace_id: project.namespace.to_param, - project_id: project.to_param) end it 'accepts the request' do + get(:new, + namespace_id: project.namespace.to_param, + project_id: project.to_param) + expect(response).to have_http_status(200) end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7ea3ea4f376..e019541e74f 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe Projects::MergeRequestsController do + include ApiHelpers + let(:project) { create(:project) } let(:user) { create(:user) } let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) } @@ -455,7 +457,7 @@ describe Projects::MergeRequestsController do it 'renders the diffs template to a string' do expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + expect(json_response).to have_key('html') end end @@ -494,7 +496,7 @@ describe Projects::MergeRequestsController do it 'renders the diffs template to a string' do expect(response).to render_template('projects/merge_requests/show/_diffs') - expect(JSON.parse(response.body)).to have_key('html') + expect(json_response).to have_key('html') end end end @@ -662,18 +664,45 @@ describe Projects::MergeRequestsController do go format: 'json' expect(response).to render_template('projects/merge_requests/show/_commits') - expect(JSON.parse(response.body)).to have_key('html') + expect(json_response).to have_key('html') end end end describe 'GET pipelines' do - it_behaves_like "loads labels", :pipelines + before do + create(:ci_pipeline, project: merge_request.source_project, + ref: merge_request.source_branch, + sha: merge_request.diff_head_sha) + end + + context 'when using HTML format' do + it_behaves_like "loads labels", :pipelines + end + + context 'when using JSON format' do + before do + get :pipelines, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid, + format: :json + end + + it 'responds with a rendered HTML partial' do + expect(response) + .to render_template('projects/merge_requests/show/_pipelines') + expect(json_response).to have_key 'html' + end + + it 'responds with serialized pipelines' do + expect(json_response).to have_key 'pipelines' + expect(json_response['pipelines']).not_to be_empty + end + end end describe 'GET conflicts' do - let(:json_response) { JSON.parse(response.body) } - context 'when the conflicts cannot be resolved in the UI' do before do allow_any_instance_of(Gitlab::Conflict::Parser). @@ -770,8 +799,6 @@ describe Projects::MergeRequestsController do end describe 'GET conflict_for_path' do - let(:json_response) { JSON.parse(response.body) } - def conflict_for_path(path) get :conflict_for_path, namespace_id: merge_request_with_conflicts.project.namespace.to_param, @@ -826,7 +853,6 @@ describe Projects::MergeRequestsController do end context 'POST resolve_conflicts' do - let(:json_response) { JSON.parse(response.body) } let!(:original_head_sha) { merge_request_with_conflicts.diff_head_sha } def resolve_conflicts(files) @@ -1024,7 +1050,6 @@ describe Projects::MergeRequestsController do let!(:forked) { create(:project) } let!(:environment) { create(:environment, project: forked) } let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } - let(:json_response) { JSON.parse(response.body) } let(:admin) { create(:admin) } let(:merge_request) do diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb index 188d33e8ef4..c28bb0dcdae 100644 --- a/spec/features/boards/sidebar_spec.rb +++ b/spec/features/boards/sidebar_spec.rb @@ -141,6 +141,36 @@ describe 'Issue Boards', feature: true, js: true do end end end + + it 'resets assignee dropdown' do + page.within(first('.board')) do + first('.card').click + end + + page.within('.assignee') do + click_link 'Edit' + + wait_for_ajax + + page.within('.dropdown-menu-user') do + click_link user.name + + wait_for_vue_resource + end + + expect(page).to have_content(user.name) + end + + page.within(first('.board')) do + find('.card:nth-child(2)').click + end + + page.within('.assignee') do + click_link 'Edit' + + expect(page).not_to have_selector('.is-active') + end + end end context 'milestone' do diff --git a/spec/features/issues/new_branch_button_spec.rb b/spec/features/issues/new_branch_button_spec.rb index a4d3053d10c..c0ab42c6822 100644 --- a/spec/features/issues/new_branch_button_spec.rb +++ b/spec/features/issues/new_branch_button_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'Start new branch from an issue', feature: true do +feature 'Start new branch from an issue', feature: true, js: true do let!(:project) { create(:project) } let!(:issue) { create(:issue, project: project) } let!(:user) { create(:user)} @@ -11,7 +11,7 @@ feature 'Start new branch from an issue', feature: true do login_as(user) end - it 'shows the new branch button', js: true do + it 'shows the new branch button' do visit namespace_project_issue_path(project.namespace, project, issue) expect(page).to have_css('#new-branch .available') @@ -34,16 +34,26 @@ feature 'Start new branch from an issue', feature: true do visit namespace_project_issue_path(project.namespace, project, issue) end - it "hides the new branch button", js: true do + it "hides the new branch button" do expect(page).to have_css('#new-branch .unavailable') expect(page).not_to have_css('#new-branch .available') expect(page).to have_content /1 Related Merge Request/ end end + + context 'when issue is confidential' do + it 'hides the new branch button' do + issue = create(:issue, :confidential, project: project) + + visit namespace_project_issue_path(project.namespace, project, issue) + + expect(page).not_to have_css('#new-branch') + end + end end - context "for visiters" do - it 'shows no buttons', js: true do + context 'for visitors' do + it 'shows no buttons' do visit namespace_project_issue_path(project.namespace, project, issue) expect(page).not_to have_css('#new-branch') diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 76bcfbe523a..ab7d89306db 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -25,6 +25,11 @@ feature 'Login', feature: true do expect(current_path).to eq root_path end + + it 'does not show flash messages when login page' do + visit root_path + expect(page).not_to have_content('You need to sign in or sign up before continuing.') + end end describe 'with two-factor authentication' do 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 b13674b4db9..2582a540240 100644 --- a/spec/features/merge_requests/user_uses_slash_commands_spec.rb +++ b/spec/features/merge_requests/user_uses_slash_commands_spec.rb @@ -11,7 +11,7 @@ feature 'Merge Requests > User uses slash commands', feature: true, js: true do it_behaves_like 'issuable record that supports slash commands in its description and notes', :merge_request do let(:issuable) { create(:merge_request, source_project: project) } - let(:new_url_opts) { { merge_request: { source_branch: 'feature' } } } + let(:new_url_opts) { { merge_request: { source_branch: 'feature', target_branch: 'master' } } } end describe 'merge-request-only commands' do diff --git a/spec/features/merge_requests/widget_spec.rb b/spec/features/merge_requests/widget_spec.rb new file mode 100644 index 00000000000..7d1805f5001 --- /dev/null +++ b/spec/features/merge_requests/widget_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +describe 'Merge request', :feature, :js do + include WaitForAjax + + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + login_as(user) + + visit new_namespace_project_merge_request_path( + project.namespace, + project, + merge_request: { + source_project_id: project.id, + target_project_id: project.id, + source_branch: 'feature', + target_branch: 'master' + } + ) + end + + it 'shows widget status after creating new merge request' do + click_button 'Submit merge request' + + expect(find('.mr-state-widget')).to have_content('Checking ability to merge automatically') + + wait_for_ajax + + expect(page).to have_selector('.accept_merge_request') + end +end diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 52d08982c7a..16dddb2a86b 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -74,6 +74,9 @@ feature 'Import/Export - project export integration test', feature: true, js: tr Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the key and the correspondent hash or model as the value. + Also, if the attribute is a generated unique token, please add it to RelationFactory::TOKEN_RESET_MODELS if it needs to be + reset (to prevent duplicate column problems while importing to the same instance). + IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file} CURRENT_SPEC: #{__FILE__} MSG diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index 7655c2b351f..20cdfbae24f 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb index c9fa8315e79..97ce9cdfd87 100644 --- a/spec/features/projects/labels/update_prioritization_spec.rb +++ b/spec/features/projects/labels/update_prioritization_spec.rb @@ -20,7 +20,7 @@ feature 'Prioritize labels', feature: true do scenario 'user can prioritize a group label', js: true do visit namespace_project_labels_path(project.namespace, project) - expect(page).to have_content('No prioritized labels yet') + expect(page).to have_content('Star labels to start sorting by priority') page.within('.other-labels') do all('.js-toggle-priority')[1].click @@ -29,7 +29,7 @@ feature 'Prioritize labels', feature: true do end page.within('.prioritized-labels') do - expect(page).not_to have_content('No prioritized labels yet') + expect(page).not_to have_content('Star labels to start sorting by priority') expect(page).to have_content('feature') end end @@ -55,7 +55,7 @@ feature 'Prioritize labels', feature: true do scenario 'user can prioritize a project label', js: true do visit namespace_project_labels_path(project.namespace, project) - expect(page).to have_content('No prioritized labels yet') + expect(page).to have_content('Star labels to start sorting by priority') page.within('.other-labels') do first('.js-toggle-priority').click @@ -64,7 +64,7 @@ feature 'Prioritize labels', feature: true do end page.within('.prioritized-labels') do - expect(page).not_to have_content('No prioritized labels yet') + expect(page).not_to have_content('Star labels to start sorting by priority') expect(page).to have_content('bug') end end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index abfc46601fb..b56e562b2b6 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -1,11 +1,13 @@ require "spec_helper" feature "New project", feature: true do + let(:user) { create(:admin) } + + before do + login_as(user) + end + context "Visibility level selector" do - let(:user) { create(:admin) } - - before { login_as(user) } - Gitlab::VisibilityLevel.options.each do |key, level| it "sets selector to #{key}" do stub_application_setting(default_project_visibility: level) @@ -16,4 +18,16 @@ feature "New project", feature: true do end end end + + context 'Import project options' do + before do + visit new_project_path + end + + it 'does not autocomplete sensitive git repo URL' do + autocomplete = find('#project_import_url')['autocomplete'] + + expect(autocomplete).to eq('off') + end + end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index e673ece37c3..917b545e98b 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -66,8 +66,8 @@ describe 'Pipeline', :feature, :js do context 'when pipeline has running builds' do it 'shows a running icon and a cancel action for the running build' do page.within('#ci-badge-deploy') do - expect(page).to have_selector('.ci-status-icon-running') - expect(page).to have_selector('.ci-action-icon-container .fa-ban') + expect(page).to have_selector('.js-ci-status-icon-running') + expect(page).to have_selector('.js-icon-action-cancel') expect(page).to have_content('deploy') end end @@ -82,12 +82,12 @@ describe 'Pipeline', :feature, :js do context 'when pipeline has successful builds' do it 'shows the success icon and a retry action for the successful build' do page.within('#ci-badge-build') do - expect(page).to have_selector('.ci-status-icon-success') + expect(page).to have_selector('.js-ci-status-icon-success') expect(page).to have_content('build') end page.within('#ci-badge-build .ci-action-icon-container') do - expect(page).to have_selector('.ci-action-icon-container .fa-refresh') + expect(page).to have_selector('.js-icon-action-retry') end end @@ -101,12 +101,12 @@ describe 'Pipeline', :feature, :js do context 'when pipeline has failed builds' do it 'shows the failed icon and a retry action for the failed build' do page.within('#ci-badge-test') do - expect(page).to have_selector('.ci-status-icon-failed') + expect(page).to have_selector('.js-ci-status-icon-failed') expect(page).to have_content('test') end page.within('#ci-badge-test .ci-action-icon-container') do - expect(page).to have_selector('.ci-action-icon-container .fa-refresh') + expect(page).to have_selector('.js-icon-action-retry') end end @@ -120,12 +120,12 @@ describe 'Pipeline', :feature, :js do context 'when pipeline has manual builds' do it 'shows the skipped icon and a play action for the manual build' do page.within('#ci-badge-manual-build') do - expect(page).to have_selector('.ci-status-icon-manual') + expect(page).to have_selector('.js-ci-status-icon-manual') expect(page).to have_content('manual') end page.within('#ci-badge-manual-build .ci-action-icon-container') do - expect(page).to have_selector('.ci-action-icon-container .fa-play') + expect(page).to have_selector('.js-icon-action-play') end end @@ -138,7 +138,7 @@ describe 'Pipeline', :feature, :js do context 'when pipeline has external build' do it 'shows the success icon and the generic comit status build' do - expect(page).to have_selector('.ci-status-icon-success') + expect(page).to have_selector('.js-ci-status-icon-success') expect(page).to have_content('jenkins') expect(page).to have_link('jenkins', href: 'http://gitlab.com/status') end diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb index 86a07b2c679..042a1ccab51 100644 --- a/spec/features/projects/services/mattermost_slash_command_spec.rb +++ b/spec/features/projects/services/mattermost_slash_command_spec.rb @@ -99,6 +99,15 @@ feature 'Setup Mattermost slash commands', feature: true do expect(select_element.all('option').count).to eq(3) end + it 'shows an error alert with the error message if there is an error requesting teams' do + allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [[], 'test mattermost error message'] } + + click_link 'Add to Mattermost' + + expect(page).to have_selector('.alert') + expect(page).to have_content('test mattermost error message') + end + def stub_teams(count: 0) teams = create_teams(count) diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index 3850e930b6d..1b352be9331 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -171,7 +171,7 @@ describe 'Dashboard Todos', feature: true do it 'links to the pipelines for the merge request' do href = pipelines_namespace_project_merge_request_path(project.namespace, project, todo.target) - expect(page).to have_link "merge request #{todo.target.to_reference}", href + expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href end end end diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index a4f08dc4af0..df71680e44c 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -115,6 +115,46 @@ describe IssuablesHelper do end end + describe '#issuable_reference' do + context 'when show_full_reference truthy' do + it 'display issuable full reference' do + assign(:show_full_reference, true) + issue = build_stubbed(:issue) + + expect(helper.issuable_reference(issue)).to eql(issue.to_reference(full: true)) + end + end + + context 'when show_full_reference falsey' do + context 'when @group present' do + it 'display issuable reference to @group' do + project = build_stubbed(:project) + + assign(:show_full_reference, nil) + assign(:group, project.namespace) + + issue = build_stubbed(:issue) + + expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project.namespace)) + end + end + + context 'when @project present' do + it 'display issuable reference to @project' do + project = build_stubbed(:project) + + assign(:show_full_reference, nil) + assign(:group, nil) + assign(:project, project) + + issue = build_stubbed(:issue) + + expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project)) + end + end + end + end + describe '#issuable_filter_present?' do it 'returns true when any key is present' do allow(helper).to receive(:params).and_return( diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index e51720f10ed..b7e547dc1f5 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -41,6 +41,11 @@ describe SearchHelper do expect(search_autocomplete_opts("gro").size).to eq(1) end + it "includes nested group" do + create(:group, :nested, name: 'foo').add_owner(user) + expect(search_autocomplete_opts('foo').size).to eq(1) + end + it "includes the user's projects" do project = create(:empty_project, namespace: create(:namespace, owner: user)) expect(search_autocomplete_opts(project.name).size).to eq(1) diff --git a/spec/javascripts/gl_form_spec.js.es6 b/spec/javascripts/gl_form_spec.js.es6 new file mode 100644 index 00000000000..71d6e2a7e22 --- /dev/null +++ b/spec/javascripts/gl_form_spec.js.es6 @@ -0,0 +1,123 @@ +/* global autosize */ + +window.autosize = require('vendor/autosize'); +require('~/gl_form'); +require('~/lib/utils/text_utility'); +require('~/lib/utils/common_utils'); + +describe('GLForm', () => { + const global = window.gl || (window.gl = {}); + const GLForm = global.GLForm; + + it('should be defined in the global scope', () => { + expect(GLForm).toBeDefined(); + }); + + describe('when instantiated', function () { + beforeEach((done) => { + this.form = $('