summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md11
-rw-r--r--app/assets/javascripts/behaviors/toggler_behavior.js26
-rw-r--r--app/assets/javascripts/environments/components/environment.js.es62
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js.es69
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.es64
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js.es61
-rw-r--r--app/assets/javascripts/merge_request_widget.js.es622
-rw-r--r--app/assets/javascripts/shortcuts_issuable.js9
-rw-r--r--app/assets/stylesheets/framework/buttons.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss6
-rw-r--r--app/assets/stylesheets/framework/nav.scss11
-rw-r--r--app/controllers/concerns/spammable_actions.rb2
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb3
-rw-r--r--app/controllers/projects/snippets_controller.rb8
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/helpers/blob_helper.rb4
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb4
-rw-r--r--app/models/ci/build.rb32
-rw-r--r--app/models/concerns/spammable.rb8
-rw-r--r--app/models/project_services/chat_slash_commands_service.rb14
-rw-r--r--app/models/project_snippet.rb4
-rw-r--r--app/models/snippet.rb14
-rw-r--r--app/services/create_snippet_service.rb9
-rw-r--r--app/views/layouts/nav/_admin.html.haml2
-rw-r--r--app/views/projects/commit/_commit_box.html.haml5
-rw-r--r--app/views/projects/diffs/_file.html.haml2
-rw-r--r--app/views/projects/environments/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/conflicts/_file_actions.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml5
-rw-r--r--app/views/projects/merge_requests/widget/_show.html.haml4
-rw-r--r--app/views/projects/pipelines/_info.html.haml4
-rw-r--r--app/views/projects/snippets/_actions.html.haml5
-rw-r--r--app/views/projects/snippets/edit.html.haml2
-rw-r--r--app/views/projects/snippets/new.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/snippets/_actions.html.haml5
-rw-r--r--app/views/snippets/edit.html.haml2
-rw-r--r--app/views/snippets/new.html.haml2
-rw-r--r--changelogs/unreleased/19164-mobile-settings.yml4
-rw-r--r--changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml4
-rw-r--r--changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml4
-rw-r--r--changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml4
-rw-r--r--changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml4
-rw-r--r--changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml4
-rw-r--r--changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml4
-rw-r--r--changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml4
-rw-r--r--changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml4
-rw-r--r--changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml4
-rw-r--r--changelogs/unreleased/27484-environment-show-name.yml4
-rw-r--r--changelogs/unreleased/27494-environment-list-column-headers.yml4
-rw-r--r--changelogs/unreleased/empty-selection-reply-shortcut.yml4
-rw-r--r--changelogs/unreleased/fix-26518.yml4
-rw-r--r--changelogs/unreleased/fix-depr-warn.yml4
-rw-r--r--changelogs/unreleased/fix-filtering-username-with-multiple-words.yml4
-rw-r--r--changelogs/unreleased/fix-import-user-validation-error.yml4
-rw-r--r--changelogs/unreleased/issue-20428.yml4
-rw-r--r--changelogs/unreleased/label-select-toggle.yml4
-rw-r--r--changelogs/unreleased/refresh-authorizations-fork-join.yml4
-rw-r--r--changelogs/unreleased/revert-filter-assigned-to-me.yml4
-rw-r--r--changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml4
-rw-r--r--changelogs/unreleased/snippet-spam.yml4
-rw-r--r--changelogs/unreleased/zj-format-chat-messages.yml4
-rw-r--r--config/routes/project.rb1
-rw-r--r--config/routes/snippets.rb1
-rw-r--r--db/migrate/20161114024742_add_coverage_regex_to_builds.rb13
-rw-r--r--db/schema.rb1
-rw-r--r--doc/ci/yaml/README.md38
-rw-r--r--doc/development/ux_guide/animation.md2
-rw-r--r--features/dashboard/shortcuts.feature21
-rw-r--r--features/steps/dashboard/shortcuts.rb7
-rw-r--r--lib/api/project_snippets.rb2
-rw-r--r--lib/api/snippets.rb2
-rw-r--r--lib/ci/gitlab_ci_yaml_processor.rb1
-rw-r--r--lib/gitlab/auth.rb11
-rw-r--r--lib/gitlab/chat_commands/base_command.rb4
-rw-r--r--lib/gitlab/chat_commands/command.rb35
-rw-r--r--lib/gitlab/chat_commands/deploy.rb24
-rw-r--r--lib/gitlab/chat_commands/help.rb28
-rw-r--r--lib/gitlab/chat_commands/issue_new.rb (renamed from lib/gitlab/chat_commands/issue_create.rb)20
-rw-r--r--lib/gitlab/chat_commands/issue_search.rb8
-rw-r--r--lib/gitlab/chat_commands/issue_show.rb8
-rw-r--r--lib/gitlab/chat_commands/presenter.rb131
-rw-r--r--lib/gitlab/chat_commands/presenters/access.rb40
-rw-r--r--lib/gitlab/chat_commands/presenters/base.rb77
-rw-r--r--lib/gitlab/chat_commands/presenters/deploy.rb21
-rw-r--r--lib/gitlab/chat_commands/presenters/help.rb27
-rw-r--r--lib/gitlab/chat_commands/presenters/issuable.rb43
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_new.rb50
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_search.rb47
-rw-r--r--lib/gitlab/chat_commands/presenters/issue_show.rb61
-rw-r--r--lib/gitlab/ci/config/entry/coverage.rb22
-rw-r--r--lib/gitlab/ci/config/entry/global.rb5
-rw-r--r--lib/gitlab/ci/config/entry/job.rb8
-rw-r--r--lib/gitlab/ci/config/entry/legacy_validation_helpers.rb10
-rw-r--r--lib/gitlab/ci/config/entry/trigger.rb10
-rw-r--r--lib/gitlab/ci/config/entry/validators.rb45
-rw-r--r--lib/gitlab/import_export/members_mapper.rb2
-rw-r--r--spec/controllers/projects/snippets_controller_spec.rb84
-rw-r--r--spec/controllers/snippets_controller_spec.rb59
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb29
-rw-r--r--spec/features/environment_spec.rb4
-rw-r--r--spec/features/environments_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb15
-rw-r--r--spec/features/merge_requests/toggler_behavior_spec.rb28
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb6
-rw-r--r--spec/helpers/diff_helper_spec.rb10
-rw-r--r--spec/javascripts/awards_handler_spec.js2
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js.es640
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js.es61
-rw-r--r--spec/javascripts/merge_request_widget_spec.js26
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js42
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb34
-rw-r--r--spec/lib/gitlab/auth_spec.rb96
-rw-r--r--spec/lib/gitlab/chat_commands/command_spec.rb50
-rw-r--r--spec/lib/gitlab/chat_commands/deploy_spec.rb24
-rw-r--r--spec/lib/gitlab/chat_commands/issue_new_spec.rb (renamed from spec/lib/gitlab/chat_commands/issue_create_spec.rb)14
-rw-r--r--spec/lib/gitlab/chat_commands/issue_search_spec.rb12
-rw-r--r--spec/lib/gitlab/chat_commands/issue_show_spec.rb25
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/access_spec.rb49
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb47
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb17
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb23
-rw-r--r--spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb37
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb54
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb17
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb14
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb8
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb24
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/models/ci/build_spec.rb41
-rw-r--r--spec/models/cycle_analytics/code_spec.rb22
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb12
-rw-r--r--spec/models/cycle_analytics/production_spec.rb18
-rw-r--r--spec/models/cycle_analytics/review_spec.rb4
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb18
-rw-r--r--spec/models/cycle_analytics/test_spec.rb50
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/requests/api/builds_spec.rb2
-rw-r--r--spec/requests/api/groups_spec.rb8
-rw-r--r--spec/requests/api/project_snippets_spec.rb50
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/snippets_spec.rb32
-rw-r--r--spec/requests/ci/api/builds_spec.rb4
-rw-r--r--spec/services/event_create_service_spec.rb6
-rw-r--r--spec/services/merge_requests/close_service_spec.rb2
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb50
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb2
-rw-r--r--spec/workers/project_destroy_worker_spec.rb4
154 files changed, 1805 insertions, 612 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 702a4ba89a8..25e02b1ae1c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 8.16.3 (2017-01-27)
+
+- Add caching of droplab ajax requests. !8725
+- Fix access to the wiki code via HTTP when repository feature disabled. !8758
+- Revert 3f17f29a. !8785
+- Fix race conditions for AuthorizedProjectsWorker.
+- Fix autocomplete initial undefined state.
+- Fix Error 500 when repositories contain annotated tags pointing to blobs.
+- Fix /explore sorting.
+- Fixed label dropdown toggle text not correctly updating.
+
## 8.16.2 (2017-01-25)
- allow issue filter bar to be operated with mouse only. !8681
diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js
index 6a49715590c..a7181904ac9 100644
--- a/app/assets/javascripts/behaviors/toggler_behavior.js
+++ b/app/assets/javascripts/behaviors/toggler_behavior.js
@@ -1,6 +1,19 @@
/* eslint-disable wrap-iife, func-names, space-before-function-paren, prefer-arrow-callback, vars-on-top, no-var, max-len */
(function(w) {
$(function() {
+ var toggleContainer = function(container, /* optional */toggleState) {
+ var $container = $(container);
+
+ $container
+ .find('.js-toggle-button .fa')
+ .toggleClass('fa-chevron-up', toggleState)
+ .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
+
+ $container
+ .find('.js-toggle-content')
+ .toggle(toggleState);
+ };
+
// Toggle button. Show/hide content inside parent container.
// Button does not change visibility. If button has icon - it changes chevron style.
//
@@ -10,14 +23,7 @@
//
$('body').on('click', '.js-toggle-button', function(e) {
e.preventDefault();
- $(this)
- .find('.fa')
- .toggleClass('fa-chevron-down fa-chevron-up')
- .end()
- .closest('.js-toggle-container')
- .find('.js-toggle-content')
- .toggle()
- ;
+ toggleContainer($(this).closest('.js-toggle-container'));
});
// If we're accessing a permalink, ensure it is not inside a
@@ -26,8 +32,8 @@
var anchor = hash && document.getElementById(hash);
var container = anchor && $(anchor).closest('.js-toggle-container');
- if (container && container.find('.js-toggle-content').is(':hidden')) {
- container.find('.js-toggle-button').trigger('click');
+ if (container) {
+ toggleContainer(container, true);
anchor.scrollIntoView();
}
});
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js.es6
index 61d0dc51ba2..26bce6c1fcd 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js.es6
@@ -182,7 +182,7 @@ require('./environment_item');
<th class="environments-deploy">Last deployment</th>
<th class="environments-build">Build</th>
<th class="environments-commit">Commit</th>
- <th class="environments-date">Created</th>
+ <th class="environments-date">Updated</th>
<th class="hidden-xs environments-actions"></th>
</tr>
</thead>
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
index 336ff48f923..f93605a5a21 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js.es6
@@ -39,8 +39,15 @@ require('./filtered_search_dropdown');
getSearchInput() {
const query = gl.DropdownUtils.getSearchInput(this.input);
const { lastToken } = gl.FilteredSearchTokenizer.processTokens(query);
+ let value = lastToken.value || '';
- return lastToken.value || '';
+ // Removes the first character if it is a quotation so that we can search
+ // with multiple words
+ if (value[0] === '"' || value[0] === '\'') {
+ value = value.slice(1);
+ }
+
+ return value;
}
init() {
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6
index 3f23095dad9..7f1f2a5d278 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js.es6
@@ -83,12 +83,12 @@
_a = decodeURI("%C3%80");
_y = decodeURI("%C3%BF");
- regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]*)$", 'gi');
+ regexp = new RegExp("^(?:\\B|[^a-zA-Z0-9_" + atSymbolsWithoutBar + "]|\\s)" + flag + "(?![" + atSymbolsWithBar + "])(([A-Za-z" + _a + "-" + _y + "0-9_\'\.\+\-]|[^\\x00-\\x7a])*)$", 'gi');
match = regexp.exec(subtext);
if (match) {
- return match[2] || match[1];
+ return (match[1] || match[1] === "") ? match[1] : match[2];
} else {
return null;
}
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js.es6
index 51993bb3420..e3bff2559fd 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js.es6
@@ -162,6 +162,7 @@
w.gl.utils.getSelectedFragment = () => {
const selection = window.getSelection();
+ if (selection.rangeCount === 0) return null;
const documentFragment = selection.getRangeAt(0).cloneContents();
if (documentFragment.textContent.length === 0) return null;
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6
index 37583dc5944..7af91e8a71b 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js.es6
@@ -156,12 +156,22 @@ require('./smart_interval');
return;
}
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
- if (data.status !== _this.opts.ci_status && (data.status != null)) {
+ if (data.status !== _this.opts.ci_status ||
+ data.sha !== _this.opts.ci_sha ||
+ data.pipeline !== _this.opts.ci_pipeline) {
_this.opts.ci_status = data.status;
_this.showCIStatus(data.status);
if (data.coverage) {
_this.showCICoverage(data.coverage);
}
+ if (data.pipeline) {
+ _this.opts.ci_pipeline = data.pipeline;
+ _this.updatePipelineUrls(data.pipeline);
+ }
+ if (data.sha) {
+ _this.opts.ci_sha = data.sha;
+ _this.updateCommitUrls(data.sha);
+ }
if (showNotification) {
status = _this.ciLabelForStatus(data.status);
if (status === "preparing") {
@@ -250,6 +260,16 @@ require('./smart_interval');
return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-info btn-create').addClass(css_class);
};
+ MergeRequestWidget.prototype.updatePipelineUrls = function(id) {
+ const pipelineUrl = this.opts.pipeline_path;
+ $('.pipeline').text(`#${id}`).attr('href', [pipelineUrl, id].join('/'));
+ };
+
+ MergeRequestWidget.prototype.updateCommitUrls = function(id) {
+ const commitsUrl = this.opts.commits_path;
+ $('.js-commit-link').text(`#${id}`).attr('href', [commitsUrl, id].join('/'));
+ };
+
return MergeRequestWidget;
})();
})(window.gl || (window.gl = {}));
diff --git a/app/assets/javascripts/shortcuts_issuable.js b/app/assets/javascripts/shortcuts_issuable.js
index b2964314bfd..0066e5bef7d 100644
--- a/app/assets/javascripts/shortcuts_issuable.js
+++ b/app/assets/javascripts/shortcuts_issuable.js
@@ -39,17 +39,20 @@ require('./shortcuts_navigation');
}
ShortcutsIssuable.prototype.replyWithSelectedText = function() {
- var quote, replyField, documentFragment, selected, separator;
+ var quote, documentFragment, selected, separator;
+ var replyField = $('.js-main-target-form #note_note');
documentFragment = window.gl.utils.getSelectedFragment();
- if (!documentFragment) return;
+ if (!documentFragment) {
+ replyField.focus();
+ return;
+ }
// If the documentFragment contains more than just Markdown, don't copy as GFM.
if (documentFragment.querySelector('.md, .wiki')) return;
selected = window.gl.CopyAsGFM.nodeToGFM(documentFragment);
- replyField = $('.js-main-target-form #note_note');
if (selected.trim() === "") {
return;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index bb6129158d9..cda46223492 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -330,10 +330,6 @@
}
}
-.btn-file-option {
- background: linear-gradient(180deg, $white-light 25%, $gray-light 100%);
-}
-
.btn-build {
margin-left: 10px;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 868f28cd356..db8d231a82a 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -58,3 +58,9 @@
fill: $gl-text-color;
}
}
+
+.icon-link {
+ &:hover {
+ text-decoration: none;
+ }
+}
diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss
index 401c2d0f6ee..fd081c2d7e1 100644
--- a/app/assets/stylesheets/framework/nav.scss
+++ b/app/assets/stylesheets/framework/nav.scss
@@ -294,16 +294,18 @@
.container-fluid {
position: relative;
+
+ .nav-control {
+ @media (max-width: $screen-sm-max) {
+ margin-right: 75px;
+ }
+ }
}
.controls {
float: right;
padding: 7px 0 0;
- @media (max-width: $screen-sm-max) {
- display: none;
- }
-
i {
color: $layout-link-gray;
}
@@ -361,6 +363,7 @@
.fade-left {
@include fade(right, $gray-light);
left: -5px;
+ text-align: center;
.fa {
left: -7px;
diff --git a/app/controllers/concerns/spammable_actions.rb b/app/controllers/concerns/spammable_actions.rb
index 99acd98ae13..562f92bd83c 100644
--- a/app/controllers/concerns/spammable_actions.rb
+++ b/app/controllers/concerns/spammable_actions.rb
@@ -7,7 +7,7 @@ module SpammableActions
def mark_as_spam
if SpamService.new(spammable).mark_as_spam!
- redirect_to spammable, notice: "#{spammable.class} was submitted to Akismet successfully."
+ redirect_to spammable, notice: "#{spammable.spammable_entity_type.titlecase} was submitted to Akismet successfully."
else
redirect_to spammable, alert: 'Error with Akismet. Please check the logs for more info.'
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index f81237db991..264b14713fb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
- @group.reset_path!
+ @group.restore_path!
render action: "edit"
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 3492502e296..6eb542e4bd8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -434,7 +434,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
title: merge_request.title,
sha: (merge_request.diff_head_commit.short_id if merge_request.diff_head_sha),
status: status,
- coverage: coverage
+ coverage: coverage,
+ pipeline: pipeline.try(:id)
}
render json: response
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 02a97c1c574..5d193f26a8e 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -1,8 +1,9 @@
class Projects::SnippetsController < Projects::ApplicationController
include ToggleAwardEmoji
+ include SpammableActions
before_action :module_enabled
- before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji]
+ before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
# Allow read any snippet
before_action :authorize_read_project_snippet!, except: [:new, :create, :index]
@@ -36,8 +37,8 @@ class Projects::SnippetsController < Projects::ApplicationController
end
def create
- @snippet = CreateSnippetService.new(@project, current_user,
- snippet_params).execute
+ create_params = snippet_params.merge(request: request)
+ @snippet = CreateSnippetService.new(@project, current_user, create_params).execute
if @snippet.valid?
respond_with(@snippet,
@@ -88,6 +89,7 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
alias_method :awardable, :snippet
+ alias_method :spammable, :snippet
def authorize_read_project_snippet!
return render_404 unless can?(current_user, :read_project_snippet, @snippet)
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index dee57e4a388..b169d993688 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,5 +1,6 @@
class SnippetsController < ApplicationController
include ToggleAwardEmoji
+ include SpammableActions
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download]
@@ -40,8 +41,8 @@ class SnippetsController < ApplicationController
end
def create
- @snippet = CreateSnippetService.new(nil, current_user,
- snippet_params).execute
+ create_params = snippet_params.merge(request: request)
+ @snippet = CreateSnippetService.new(nil, current_user, create_params).execute
respond_with @snippet.becomes(Snippet)
end
@@ -96,6 +97,7 @@ class SnippetsController < ApplicationController
end
end
alias_method :awardable, :snippet
+ alias_method :spammable, :snippet
def authorize_read_snippet!
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index c3508443d8a..311a70725ab 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -21,7 +21,7 @@ module BlobHelper
options[:link_opts])
if !on_top_of_branch?(project, ref)
- button_tag "Edit", class: "btn disabled has-tooltip btn-file-option", title: "You can only edit files when you are on a branch", data: { container: 'body' }
+ button_tag "Edit", class: "btn disabled has-tooltip", title: "You can only edit files when you are on a branch", data: { container: 'body' }
elsif can_edit_blob?(blob, project, ref)
link_to "Edit", edit_path, class: 'btn btn-sm'
elsif can?(current_user, :fork_project, project)
@@ -32,7 +32,7 @@ module BlobHelper
}
fork_path = namespace_project_forks_path(project.namespace, project, namespace_key: current_user.namespace.id, continue: continue_params)
- link_to "Edit", fork_path, class: 'btn btn-file-option', method: :post
+ link_to "Edit", fork_path, class: 'btn', method: :post
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index e9461b9f859..6dcb624c4da 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -198,7 +198,7 @@ module CommitsHelper
link_to(
namespace_project_blob_path(project.namespace, project,
tree_join(commit_sha, diff_new_path)),
- class: 'btn view-file js-view-file btn-file-option'
+ class: 'btn view-file js-view-file'
) do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 3a83ae15dd8..fc93acfe63e 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -93,10 +93,6 @@ module VisibilityLevelHelper
current_application_settings.default_project_visibility
end
- def default_snippet_visibility
- current_application_settings.default_snippet_visibility
- end
-
def default_group_visibility
current_application_settings.default_group_visibility
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5fe8ddf69d7..b1f77bf242c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -275,29 +275,23 @@ module Ci
end
def update_coverage
- return unless project
- coverage_regex = project.build_coverage_regex
- return unless coverage_regex
coverage = extract_coverage(trace, coverage_regex)
-
- if coverage.is_a? Numeric
- update_attributes(coverage: coverage)
- end
+ update_attributes(coverage: coverage) if coverage.present?
end
def extract_coverage(text, regex)
- begin
- matches = text.scan(Regexp.new(regex)).last
- matches = matches.last if matches.kind_of?(Array)
- coverage = matches.gsub(/\d+(\.\d+)?/).first
+ return unless regex
- if coverage.present?
- coverage.to_f
- end
- rescue
- # if bad regex or something goes wrong we dont want to interrupt transition
- # so we just silentrly ignore error for now
+ matches = text.scan(Regexp.new(regex)).last
+ matches = matches.last if matches.kind_of?(Array)
+ coverage = matches.gsub(/\d+(\.\d+)?/).first
+
+ if coverage.present?
+ coverage.to_f
end
+ rescue
+ # if bad regex or something goes wrong we dont want to interrupt transition
+ # so we just silentrly ignore error for now
end
def has_trace_file?
@@ -522,6 +516,10 @@ module Ci
self.update(artifacts_expire_at: nil)
end
+ def coverage_regex
+ super || project.try(:build_coverage_regex)
+ end
+
def when
read_attribute(:when) || build_attributes_from_config[:when] || 'on_success'
end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 1aa97debe42..1acff093aa1 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -34,7 +34,13 @@ module Spammable
end
def check_for_spam
- self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam?
+ if spam?
+ self.errors.add(:base, "Your #{spammable_entity_type} has been recognized as spam and has been discarded.")
+ end
+ end
+
+ def spammable_entity_type
+ self.class.name.underscore
end
def spam_title
diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb
index 2bcff541cc0..5eb1bd86e9d 100644
--- a/app/models/project_services/chat_slash_commands_service.rb
+++ b/app/models/project_services/chat_slash_commands_service.rb
@@ -31,13 +31,13 @@ class ChatSlashCommandsService < Service
return unless valid_token?(params[:token])
user = find_chat_user(params)
- unless user
+
+ if user
+ Gitlab::ChatCommands::Command.new(project, user, params).execute
+ else
url = authorize_chat_name_url(params)
- return presenter.authorize_chat_name(url)
+ Gitlab::ChatCommands::Presenters::Access.new(url).authorize
end
-
- Gitlab::ChatCommands::Command.new(project, user,
- params).execute
end
private
@@ -49,8 +49,4 @@ class ChatSlashCommandsService < Service
def authorize_chat_name_url(params)
ChatNames::AuthorizeUserService.new(self, params).execute
end
-
- def presenter
- Gitlab::ChatCommands::Presenter.new
- end
end
diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb
index 25b5d777641..9bb456eee24 100644
--- a/app/models/project_snippet.rb
+++ b/app/models/project_snippet.rb
@@ -9,4 +9,8 @@ class ProjectSnippet < Snippet
participant :author
participant :notes_with_associations
+
+ def check_for_spam?
+ super && project.public?
+ end
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 771a7350556..2665a7249a3 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -7,6 +7,7 @@ class Snippet < ActiveRecord::Base
include Sortable
include Awardable
include Mentionable
+ include Spammable
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :content
@@ -17,7 +18,7 @@ class Snippet < ActiveRecord::Base
default_content_html_invalidator || file_name_changed?
end
- default_value_for :visibility_level, Snippet::PRIVATE
+ default_value_for(:visibility_level) { current_application_settings.default_snippet_visibility }
belongs_to :author, class_name: 'User'
belongs_to :project
@@ -46,6 +47,9 @@ class Snippet < ActiveRecord::Base
participant :author
participant :notes_with_associations
+ attr_spammable :title, spam_title: true
+ attr_spammable :content, spam_description: true
+
def self.reference_prefix
'$'
end
@@ -127,6 +131,14 @@ class Snippet < ActiveRecord::Base
notes.includes(:author)
end
+ def check_for_spam?
+ public?
+ end
+
+ def spammable_entity_type
+ 'snippet'
+ end
+
class << self
# Searches for snippets with a matching title or file name.
#
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 95cc9baf406..14f5ba064ff 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -1,5 +1,8 @@
class CreateSnippetService < BaseService
def execute
+ request = params.delete(:request)
+ api = params.delete(:api)
+
snippet = if project
project.snippets.build(params)
else
@@ -12,8 +15,12 @@ class CreateSnippetService < BaseService
end
snippet.author = current_user
+ snippet.spam = SpamService.new(snippet, request).check(api)
+
+ if snippet.save
+ UserAgentDetailService.new(snippet, request).create
+ end
- snippet.save
snippet
end
end
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index ac04f57e217..19a947af4ca 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -1,5 +1,5 @@
+= render 'layouts/nav/admin_settings'
.scrolling-tabs-container{ class: nav_control_class }
- = render 'layouts/nav/admin_settings'
.fade-left
= icon('angle-left')
.fade-right
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 6dba42d5226..4d0b7a5ca85 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -63,9 +63,10 @@
- if @commit.status
.well-segment.pipeline-info
%div{ class: "icon-container ci-status-icon-#{@commit.status}" }
- = ci_icon_for_status(@commit.status)
+ = link_to namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id) do
+ = ci_icon_for_status(@commit.status)
Pipeline
- = link_to "##{@commit.pipelines.last.id}", pipelines_namespace_project_commit_path(@project.namespace, @project, @commit.id), class: "monospace"
+ = link_to "##{@commit.pipelines.last.id}", namespace_project_pipeline_path(@project.namespace, @project, @commit.pipelines.last.id), class: "monospace"
for
= link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace"
%span.ci-status-label
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c37a33bbcd5..fc478ccc995 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -5,7 +5,7 @@
- unless diff_file.submodule?
.file-actions.hidden-xs
- if blob_text_viewable?(blob)
- = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
+ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip', title: "Toggle comments for this file", disabled: @diff_notes_disabled do
= icon('comment')
\
- if editable_diff?(diff_file)
diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml
index 6e0d9456900..f3179dce5f2 100644
--- a/app/views/projects/environments/show.html.haml
+++ b/app/views/projects/environments/show.html.haml
@@ -5,7 +5,7 @@
%div{ class: container_class }
.top-area.adjust
.col-md-9
- %h3.page-title= @environment.name.capitalize
+ %h3.page-title= @environment.name
.col-md-3
.nav-controls
= render 'projects/environments/terminal_button', environment: @environment
@@ -33,7 +33,7 @@
%th ID
%th Commit
%th Build
- %th
+ %th Created
%th.hidden-xs
= render @deployments
diff --git a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
index 2595ce74ac0..0839880713f 100644
--- a/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
+++ b/app/views/projects/merge_requests/conflicts/_file_actions.html.haml
@@ -8,5 +8,5 @@
'@click' => "onClickResolveModeButton(file, 'edit')",
type: 'button' }
Edit inline
- %a.btn.view-file.btn-file-option{ ":href" => "file.blobPath" }
+ %a.btn.view-file{ ":href" => "file.blobPath" }
View file @{{conflictsData.shortCommitSha}}
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 5faa6c43f9f..0e3af62ebc2 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -2,14 +2,15 @@
.mr-widget-heading
- %w[success success_with_warnings skipped canceled failed running pending].each do |status|
.ci_widget{ class: "ci-#{status} ci-status-icon-#{status}", style: ("display:none" unless @pipeline.status == status) }
- = ci_icon_for_status(status)
+ = link_to namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'icon-link' do
+ = ci_icon_for_status(status)
%span
Pipeline
= link_to "##{@pipeline.id}", namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'pipeline'
= ci_label_for_status(status)
for
= succeed "." do
- = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace"
+ = link_to @pipeline.short_sha, namespace_project_commit_path(@merge_request.source_project.namespace, @merge_request.source_project, @pipeline.sha), class: "monospace js-commit-link"
%span.ci-coverage
- elsif @merge_request.has_ci?
diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml
index 38328501ffd..f07e6b3ad54 100644
--- a/app/views/projects/merge_requests/widget/_show.html.haml
+++ b/app/views/projects/merge_requests/widget/_show.html.haml
@@ -24,6 +24,10 @@
preparing: "{{status}} build",
normal: "Build {{status}}"
},
+ ci_sha: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.short_sha : ''}",
+ ci_pipeline: #{@merge_request.head_pipeline.try(:id).to_json},
+ commits_path: "#{project_commits_path(@project)}",
+ pipeline_path: "#{project_pipelines_path(@project)}",
pipelines_path: "#{pipelines_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}"
};
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index ca76f13ef5e..6caa5f16dc6 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -23,8 +23,8 @@
.info-well
- if @commit.status
.well-segment.pipeline-info
- %div{ class: "icon-container ci-status-icon-#{@commit.status}" }
- = ci_icon_for_status(@commit.status)
+ .icon-container
+ = icon('clock-o')
= pluralize @pipeline.statuses.count(:id), "build"
- if @pipeline.ref
from
diff --git a/app/views/projects/snippets/_actions.html.haml b/app/views/projects/snippets/_actions.html.haml
index 068a6610350..e2a5107a883 100644
--- a/app/views/projects/snippets/_actions.html.haml
+++ b/app/views/projects/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
- if can?(current_user, :create_project_snippet, @project)
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: 'btn btn-grouped btn-inverted btn-create', title: "New snippet" do
New snippet
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if can?(current_user, :create_project_snippet, @project) || can?(current_user, :update_project_snippet, @snippet)
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -27,3 +29,6 @@
%li
= link_to edit_namespace_project_snippet_path(@project.namespace, @project, @snippet) do
Edit
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ %li
+ = link_to 'Submit as spam', mark_as_spam_namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :post
diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml
index 216f70f5605..fb39028529d 100644
--- a/app/views/projects/snippets/edit.html.haml
+++ b/app/views/projects/snippets/edit.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
Edit Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet), visibility_level: @snippet.visibility_level
+= render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet)
diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml
index 772a594269c..cfed3a79bc5 100644
--- a/app/views/projects/snippets/new.html.haml
+++ b/app/views/projects/snippets/new.html.haml
@@ -3,4 +3,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet)
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index b5362a7d5d5..56c0f7390a5 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -11,7 +11,7 @@
.col-sm-10
= f.text_field :title, class: 'form-control', required: true, autofocus: true
- = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet
+ = render 'shared/visibility_level', f: f, visibility_level: @snippet.visibility_level, can_change_visibility_level: true, form_model: @snippet
.file-editor
.form-group
diff --git a/app/views/snippets/_actions.html.haml b/app/views/snippets/_actions.html.haml
index 95fc7198104..9a9a3ff9220 100644
--- a/app/views/snippets/_actions.html.haml
+++ b/app/views/snippets/_actions.html.haml
@@ -8,6 +8,8 @@
- if current_user
= link_to new_snippet_path, class: "btn btn-grouped btn-inverted btn-create", title: "New snippet" do
New snippet
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post, class: 'btn btn-grouped btn-spam', title: 'Submit as spam'
- if current_user
.visible-xs-block.dropdown
%button.btn.btn-default.btn-block.append-bottom-0.prepend-top-5{ data: { toggle: "dropdown" } }
@@ -26,3 +28,6 @@
%li
= link_to edit_snippet_path(@snippet) do
Edit
+ - if @snippet.submittable_as_spam? && current_user.admin?
+ %li
+ = link_to 'Submit as spam', mark_as_spam_snippet_path(@snippet), method: :post
diff --git a/app/views/snippets/edit.html.haml b/app/views/snippets/edit.html.haml
index 82f44a9a5c3..915bf98eb3e 100644
--- a/app/views/snippets/edit.html.haml
+++ b/app/views/snippets/edit.html.haml
@@ -2,4 +2,4 @@
%h3.page-title
Edit Snippet
%hr
-= render 'shared/snippets/form', url: snippet_path(@snippet), visibility_level: @snippet.visibility_level
+= render 'shared/snippets/form', url: snippet_path(@snippet)
diff --git a/app/views/snippets/new.html.haml b/app/views/snippets/new.html.haml
index 79e2392490d..ca8afb4bb6a 100644
--- a/app/views/snippets/new.html.haml
+++ b/app/views/snippets/new.html.haml
@@ -2,4 +2,4 @@
%h3.page-title
New Snippet
%hr
-= render "shared/snippets/form", url: snippets_path(@snippet), visibility_level: default_snippet_visibility
+= render "shared/snippets/form", url: snippets_path(@snippet)
diff --git a/changelogs/unreleased/19164-mobile-settings.yml b/changelogs/unreleased/19164-mobile-settings.yml
new file mode 100644
index 00000000000..c26a20f87e2
--- /dev/null
+++ b/changelogs/unreleased/19164-mobile-settings.yml
@@ -0,0 +1,4 @@
+---
+title: 19164 Add settings dropdown to mobile screens
+merge_request:
+author:
diff --git a/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
new file mode 100644
index 00000000000..05fbd8f0bf2
--- /dev/null
+++ b/changelogs/unreleased/24462-reduce_ldap_queries_for_lfs.yml
@@ -0,0 +1,4 @@
+---
+title: Reduce hits to LDAP on Git HTTP auth by reordering auth mechanisms
+merge_request: 8752
+author:
diff --git a/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
new file mode 100644
index 00000000000..f74e9fa8b6d
--- /dev/null
+++ b/changelogs/unreleased/25811-pipeline-number-and-url-do-not-update-when-new-pipeline-is-triggered.yml
@@ -0,0 +1,4 @@
+---
+title: Update pipeline and commit links when CI status is updated
+merge_request: 8351
+author:
diff --git a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml b/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml
deleted file mode 100644
index 2d4ec482ee0..00000000000
--- a/changelogs/unreleased/26775-fix-auto-complete-initial-loading.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix autocomplete initial undefined state
-merge_request:
-author:
diff --git a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml
deleted file mode 100644
index 4678297cfd4..00000000000
--- a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Add caching of droplab ajax requests
-merge_request: 8725
-author:
diff --git a/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
new file mode 100644
index 00000000000..c5c57af5aaf
--- /dev/null
+++ b/changelogs/unreleased/26982-improve-pipeline-status-icon-linking-in-widgets.yml
@@ -0,0 +1,4 @@
+---
+title: Improve pipeline status icon linking in widgets
+merge_request:
+author:
diff --git a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml
deleted file mode 100644
index 0f0a8940f72..00000000000
--- a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix /explore sorting
-merge_request:
-author:
diff --git a/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
new file mode 100644
index 00000000000..1758ed9e9ea
--- /dev/null
+++ b/changelogs/unreleased/27067-mention-user-dropdown-does-not-suggest-by-non-ascii-characters-in-name.yml
@@ -0,0 +1,4 @@
+---
+title: Support non-ASCII characters in GFM autocomplete
+merge_request: 8729
+author:
diff --git a/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
new file mode 100644
index 00000000000..ddd454da376
--- /dev/null
+++ b/changelogs/unreleased/27089-26860-27151-fix-discussion-note-permalink-collapsed.yml
@@ -0,0 +1,4 @@
+---
+title: Fix permalink discussion note being collapsed
+merge_request:
+author:
diff --git a/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
new file mode 100644
index 00000000000..293aab67d39
--- /dev/null
+++ b/changelogs/unreleased/27291-unify-mr-diff-file-buttons.yml
@@ -0,0 +1,4 @@
+---
+title: Unify MR diff file button style
+merge_request: 8874
+author:
diff --git a/changelogs/unreleased/27484-environment-show-name.yml b/changelogs/unreleased/27484-environment-show-name.yml
new file mode 100644
index 00000000000..dc400d65006
--- /dev/null
+++ b/changelogs/unreleased/27484-environment-show-name.yml
@@ -0,0 +1,4 @@
+---
+title: Don't capitalize environment name in show page
+merge_request:
+author:
diff --git a/changelogs/unreleased/27494-environment-list-column-headers.yml b/changelogs/unreleased/27494-environment-list-column-headers.yml
new file mode 100644
index 00000000000..798c01f3238
--- /dev/null
+++ b/changelogs/unreleased/27494-environment-list-column-headers.yml
@@ -0,0 +1,4 @@
+---
+title: Edited the column header for the environments list from created to updated and added created to environments detail page colum header titles
+merge_request:
+author:
diff --git a/changelogs/unreleased/empty-selection-reply-shortcut.yml b/changelogs/unreleased/empty-selection-reply-shortcut.yml
new file mode 100644
index 00000000000..5a42c98a800
--- /dev/null
+++ b/changelogs/unreleased/empty-selection-reply-shortcut.yml
@@ -0,0 +1,4 @@
+---
+title: Change the reply shortcut to focus the field even without a selection.
+merge_request: 8873
+author: Brian Hall
diff --git a/changelogs/unreleased/fix-26518.yml b/changelogs/unreleased/fix-26518.yml
deleted file mode 100644
index 961ac2642fb..00000000000
--- a/changelogs/unreleased/fix-26518.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix access to the wiki code via HTTP when repository feature disabled
-merge_request: 8758
-author:
diff --git a/changelogs/unreleased/fix-depr-warn.yml b/changelogs/unreleased/fix-depr-warn.yml
new file mode 100644
index 00000000000..61817027720
--- /dev/null
+++ b/changelogs/unreleased/fix-depr-warn.yml
@@ -0,0 +1,4 @@
+---
+title: resolve deprecation warnings
+merge_request: 8855
+author: Adam Pahlevi
diff --git a/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
new file mode 100644
index 00000000000..3513f5afdfb
--- /dev/null
+++ b/changelogs/unreleased/fix-filtering-username-with-multiple-words.yml
@@ -0,0 +1,4 @@
+---
+title: Fix filtering usernames with multiple words
+merge_request: 8851
+author:
diff --git a/changelogs/unreleased/fix-import-user-validation-error.yml b/changelogs/unreleased/fix-import-user-validation-error.yml
new file mode 100644
index 00000000000..985a3b0b26f
--- /dev/null
+++ b/changelogs/unreleased/fix-import-user-validation-error.yml
@@ -0,0 +1,4 @@
+---
+title: Remove old project members when retrying an export
+merge_request:
+author:
diff --git a/changelogs/unreleased/issue-20428.yml b/changelogs/unreleased/issue-20428.yml
new file mode 100644
index 00000000000..60da1c14702
--- /dev/null
+++ b/changelogs/unreleased/issue-20428.yml
@@ -0,0 +1,4 @@
+---
+title: Add ability to define a coverage regex in the .gitlab-ci.yml
+merge_request: 7447
+author: Leandro Camargo
diff --git a/changelogs/unreleased/label-select-toggle.yml b/changelogs/unreleased/label-select-toggle.yml
deleted file mode 100644
index af5b4246521..00000000000
--- a/changelogs/unreleased/label-select-toggle.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fixed label dropdown toggle text not correctly updating
-merge_request:
-author:
diff --git a/changelogs/unreleased/refresh-authorizations-fork-join.yml b/changelogs/unreleased/refresh-authorizations-fork-join.yml
deleted file mode 100644
index b1349b9447d..00000000000
--- a/changelogs/unreleased/refresh-authorizations-fork-join.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix race conditions for AuthorizedProjectsWorker
-merge_request:
-author:
diff --git a/changelogs/unreleased/revert-filter-assigned-to-me.yml b/changelogs/unreleased/revert-filter-assigned-to-me.yml
deleted file mode 100644
index 37f9d2f5fc4..00000000000
--- a/changelogs/unreleased/revert-filter-assigned-to-me.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Revert 3f17f29a
-merge_request: 8785
-author:
diff --git a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml
deleted file mode 100644
index ff2b38f21f2..00000000000
--- a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Fix Error 500 when repositories contain annotated tags pointing to blobs
-merge_request:
-author:
diff --git a/changelogs/unreleased/snippet-spam.yml b/changelogs/unreleased/snippet-spam.yml
new file mode 100644
index 00000000000..4867f088953
--- /dev/null
+++ b/changelogs/unreleased/snippet-spam.yml
@@ -0,0 +1,4 @@
+---
+title: Check public snippets for spam
+merge_request:
+author:
diff --git a/changelogs/unreleased/zj-format-chat-messages.yml b/changelogs/unreleased/zj-format-chat-messages.yml
new file mode 100644
index 00000000000..2494884f5c9
--- /dev/null
+++ b/changelogs/unreleased/zj-format-chat-messages.yml
@@ -0,0 +1,4 @@
+---
+title: Reformat messages ChatOps
+merge_request: 8528
+author:
diff --git a/config/routes/project.rb b/config/routes/project.rb
index f36febc6e04..efe2fbc521d 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -64,6 +64,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do
member do
get 'raw'
+ post :mark_as_spam
end
end
diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb
index 3ca096f31ba..ce0d1314292 100644
--- a/config/routes/snippets.rb
+++ b/config/routes/snippets.rb
@@ -2,6 +2,7 @@ resources :snippets, concerns: :awardable do
member do
get 'raw'
get 'download'
+ post :mark_as_spam
end
end
diff --git a/db/migrate/20161114024742_add_coverage_regex_to_builds.rb b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
new file mode 100644
index 00000000000..88aa5d52b39
--- /dev/null
+++ b/db/migrate/20161114024742_add_coverage_regex_to_builds.rb
@@ -0,0 +1,13 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddCoverageRegexToBuilds < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ add_column :ci_builds, :coverage_regex, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5efb4f6595c..c73c311ccb2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -215,6 +215,7 @@ ActiveRecord::Schema.define(version: 20170130204620) do
t.datetime "queued_at"
t.string "token"
t.integer "lock_version"
+ t.string "coverage_regex"
end
add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index f11257be5c3..06810898cfe 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -76,6 +76,7 @@ There are a few reserved `keywords` that **cannot** be used as job names:
| after_script | no | Define commands that run after each job's script |
| variables | no | Define build variables |
| cache | no | Define list of files that should be cached between subsequent runs |
+| coverage | no | Define coverage settings for all jobs |
### image and services
@@ -278,6 +279,23 @@ cache:
untracked: true
```
+### coverage
+
+`coverage` allows you to configure how coverage will be filtered out from the
+build outputs. Setting this up globally will make all the jobs to use this
+setting for output filtering and extracting the coverage information from your
+builds.
+
+Regular expressions are the only valid kind of value expected here. So, using
+surrounding `/` is mandatory in order to consistently and explicitly represent
+a regular expression string. You must escape special characters if you want to
+match them literally.
+
+A simple example:
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+```
+
## Jobs
`.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job
@@ -319,6 +337,7 @@ job_name:
| before_script | no | Override a set of commands that are executed before build |
| after_script | no | Override a set of commands that are executed after build |
| environment | no | Defines a name of environment to which deployment is done by this build |
+| coverage | no | Define coverage settings for a given job |
### script
@@ -993,6 +1012,25 @@ job:
- execute this after my script
```
+### job coverage
+
+This entry is pretty much the same as described in the global context in
+[`coverage`](#coverage). The only difference is that, by setting it inside
+the job level, whatever is set in there will take precedence over what has
+been defined in the global level. A quick example of one overriding the
+other would be:
+
+```yaml
+coverage: /\(\d+\.\d+\) covered\./
+
+job1:
+ coverage: /Code coverage: \d+\.\d+/
+```
+
+In the example above, considering the context of the job `job1`, the coverage
+regex that would be used is `/Code coverage: \d+\.\d+/` instead of
+`/\(\d+\.\d+\) covered\./`.
+
## Git Strategy
> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index 903e54bf9dc..5dae4bcc905 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -19,7 +19,7 @@ Easing specifies the rate of change of a parameter over time (see [easings.net](
### Hover
-Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `200ms linear` transition for a color hover effect.
+Interactive elements (links, buttons, etc.) should have a hover state. A subtle animation for this transition adds a polished feel. We should target a `100ms - 150ms linear` transition for a color hover effect.
View the [interactive example](http://codepen.io/awhildy/full/GNyEvM/) here.
diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature
deleted file mode 100644
index 41d79aa6ec8..00000000000
--- a/features/dashboard/shortcuts.feature
+++ /dev/null
@@ -1,21 +0,0 @@
-@dashboard
-Feature: Dashboard Shortcuts
- Background:
- Given I sign in as a user
- And I visit dashboard page
-
- @javascript
- Scenario: Navigate to projects tab
- Given I press "g" and "p"
- Then the active main tab should be Projects
-
- @javascript
- Scenario: Navigate to issue tab
- Given I press "g" and "i"
- Then the active main tab should be Issues
-
- @javascript
- Scenario: Navigate to merge requests tab
- Given I press "g" and "m"
- Then the active main tab should be Merge Requests
-
diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb
deleted file mode 100644
index 118d27888df..00000000000
--- a/features/steps/dashboard/shortcuts.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedPaths
- include SharedProject
- include SharedSidebarActiveTab
- include SharedShortcuts
-end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 9d8c5b63685..dcc0c82ee27 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -58,7 +58,7 @@ module API
end
post ":id/snippets" do
authorize! :create_project_snippet, user_project
- snippet_params = declared_params
+ snippet_params = declared_params.merge(request: request, api: true)
snippet_params[:content] = snippet_params.delete(:code)
snippet = CreateSnippetService.new(user_project, current_user, snippet_params).execute
diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb
index e096e636806..eb9ece49e7f 100644
--- a/lib/api/snippets.rb
+++ b/lib/api/snippets.rb
@@ -64,7 +64,7 @@ module API
desc: 'The visibility level of the snippet'
end
post do
- attrs = declared_params(include_missing: false)
+ attrs = declared_params(include_missing: false).merge(request: request, api: true)
snippet = CreateSnippetService.new(nil, current_user, attrs).execute
if snippet.persisted?
diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb
index 7463bd719d5..649ee4d018b 100644
--- a/lib/ci/gitlab_ci_yaml_processor.rb
+++ b/lib/ci/gitlab_ci_yaml_processor.rb
@@ -61,6 +61,7 @@ module Ci
allow_failure: job[:allow_failure] || false,
when: job[:when] || 'on_success',
environment: job[:environment_name],
+ coverage_regex: job[:coverage],
yaml_variables: yaml_variables(name),
options: {
image: job[:image],
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 8dda65c71ef..f638905a1e0 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -10,13 +10,16 @@ module Gitlab
def find_for_git_client(login, password, project:, ip:)
raise "Must provide an IP for rate limiting" if ip.nil?
+ # `user_with_password_for_git` should be the last check
+ # because it's the most expensive, especially when LDAP
+ # is enabled.
result =
service_request_check(login, password, project) ||
build_access_token_check(login, password) ||
- user_with_password_for_git(login, password) ||
- oauth_access_token_check(login, password) ||
lfs_token_check(login, password) ||
+ oauth_access_token_check(login, password) ||
personal_access_token_check(login, password) ||
+ user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login)
@@ -143,7 +146,9 @@ module Gitlab
read_authentication_abilities
end
- Result.new(actor, nil, token_handler.type, authentication_abilities) if Devise.secure_compare(token_handler.token, password)
+ if Devise.secure_compare(token_handler.token, password)
+ Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
+ end
end
def build_access_token_check(login, password)
diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb
index 4fe53ce93a9..25da8474e95 100644
--- a/lib/gitlab/chat_commands/base_command.rb
+++ b/lib/gitlab/chat_commands/base_command.rb
@@ -42,10 +42,6 @@ module Gitlab
def find_by_iid(iid)
collection.find_by(iid: iid)
end
-
- def presenter
- Gitlab::ChatCommands::Presenter.new
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb
index 145086755e4..f34ed0f4cf2 100644
--- a/lib/gitlab/chat_commands/command.rb
+++ b/lib/gitlab/chat_commands/command.rb
@@ -3,7 +3,7 @@ module Gitlab
class Command < BaseCommand
COMMANDS = [
Gitlab::ChatCommands::IssueShow,
- Gitlab::ChatCommands::IssueCreate,
+ Gitlab::ChatCommands::IssueNew,
Gitlab::ChatCommands::IssueSearch,
Gitlab::ChatCommands::Deploy,
].freeze
@@ -13,51 +13,32 @@ module Gitlab
if command
if command.allowed?(project, current_user)
- present command.new(project, current_user, params).execute(match)
+ command.new(project, current_user, params).execute(match)
else
- access_denied
+ Gitlab::ChatCommands::Presenters::Access.new.access_denied
end
else
- help(help_messages)
+ Gitlab::ChatCommands::Help.new(project, current_user, params).execute(available_commands, params[:text])
end
end
def match_command
match = nil
- service = available_commands.find do |klass|
- match = klass.match(command)
- end
+ service =
+ available_commands.find do |klass|
+ match = klass.match(params[:text])
+ end
[service, match]
end
private
- def help_messages
- available_commands.map(&:help_message)
- end
-
def available_commands
COMMANDS.select do |klass|
klass.available?(project)
end
end
-
- def command
- params[:text]
- end
-
- def help(messages)
- presenter.help(messages, params[:command])
- end
-
- def access_denied
- presenter.access_denied
- end
-
- def present(resource)
- presenter.present(resource)
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb
index 7127d2f6d04..458d90f84e8 100644
--- a/lib/gitlab/chat_commands/deploy.rb
+++ b/lib/gitlab/chat_commands/deploy.rb
@@ -1,8 +1,6 @@
module Gitlab
module ChatCommands
class Deploy < BaseCommand
- include Gitlab::Routing.url_helpers
-
def self.match(text)
/\Adeploy\s+(?<from>\S+.*)\s+to+\s+(?<to>\S+.*)\z/.match(text)
end
@@ -24,35 +22,29 @@ module Gitlab
to = match[:to]
actions = find_actions(from, to)
- return unless actions.present?
- if actions.one?
- play!(from, to, actions.first)
+ if actions.none?
+ Gitlab::ChatCommands::Presenters::Deploy.new(nil).no_actions
+ elsif actions.one?
+ action = play!(from, to, actions.first)
+ Gitlab::ChatCommands::Presenters::Deploy.new(action).present(from, to)
else
- Result.new(:error, 'Too many actions defined')
+ Gitlab::ChatCommands::Presenters::Deploy.new(actions).too_many_actions
end
end
private
def play!(from, to, action)
- new_action = action.play(current_user)
-
- Result.new(:success, "Deployment from #{from} to #{to} started. Follow the progress: #{url(new_action)}.")
+ action.play(current_user)
end
def find_actions(from, to)
environment = project.environments.find_by(name: from)
- return unless environment
+ return [] unless environment
environment.actions_for(to).select(&:starts_environment?)
end
-
- def url(subject)
- polymorphic_url(
- [subject.project.namespace.becomes(Namespace), subject.project, subject]
- )
- end
end
end
end
diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb
new file mode 100644
index 00000000000..6c0e4d304a4
--- /dev/null
+++ b/lib/gitlab/chat_commands/help.rb
@@ -0,0 +1,28 @@
+module Gitlab
+ module ChatCommands
+ class Help < BaseCommand
+ # This class has to be used last, as it always matches. It has to match
+ # because other commands were not triggered and we want to show the help
+ # command
+ def self.match(_text)
+ true
+ end
+
+ def self.help_message
+ 'help'
+ end
+
+ def self.allowed?(_project, _user)
+ true
+ end
+
+ def execute(commands, text)
+ Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger, text)
+ end
+
+ def trigger
+ params[:command]
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/issue_create.rb b/lib/gitlab/chat_commands/issue_new.rb
index cefb6775db8..016054ecd46 100644
--- a/lib/gitlab/chat_commands/issue_create.rb
+++ b/lib/gitlab/chat_commands/issue_new.rb
@@ -1,8 +1,8 @@
module Gitlab
module ChatCommands
- class IssueCreate < IssueCommand
+ class IssueNew < IssueCommand
def self.match(text)
- # we can not match \n with the dot by passing the m modifier as than
+ # we can not match \n with the dot by passing the m modifier as than
# the title and description are not seperated
/\Aissue\s+(new|create)\s+(?<title>[^\n]*)\n*(?<description>(.|\n)*)/.match(text)
end
@@ -19,8 +19,24 @@ module Gitlab
title = match[:title]
description = match[:description].to_s.rstrip
+ issue = create_issue(title: title, description: description)
+
+ if issue.persisted?
+ presenter(issue).present
+ else
+ presenter(issue).display_errors
+ end
+ end
+
+ private
+
+ def create_issue(title:, description:)
Issues::CreateService.new(project, current_user, title: title, description: description).execute
end
+
+ def presenter(issue)
+ Gitlab::ChatCommands::Presenters::IssueNew.new(issue)
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/issue_search.rb b/lib/gitlab/chat_commands/issue_search.rb
index 51bf80c800b..3491b53093e 100644
--- a/lib/gitlab/chat_commands/issue_search.rb
+++ b/lib/gitlab/chat_commands/issue_search.rb
@@ -10,7 +10,13 @@ module Gitlab
end
def execute(match)
- collection.search(match[:query]).limit(QUERY_LIMIT)
+ issues = collection.search(match[:query]).limit(QUERY_LIMIT)
+
+ if issues.present?
+ Presenters::IssueSearch.new(issues).present
+ else
+ Presenters::Access.new(issues).not_found
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/issue_show.rb b/lib/gitlab/chat_commands/issue_show.rb
index 2a45d49cf6b..d6013f4d10c 100644
--- a/lib/gitlab/chat_commands/issue_show.rb
+++ b/lib/gitlab/chat_commands/issue_show.rb
@@ -10,7 +10,13 @@ module Gitlab
end
def execute(match)
- find_by_iid(match[:iid])
+ issue = find_by_iid(match[:iid])
+
+ if issue
+ Gitlab::ChatCommands::Presenters::IssueShow.new(issue).present
+ else
+ Gitlab::ChatCommands::Presenters::Access.new.not_found
+ end
end
end
end
diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb
deleted file mode 100644
index 8930a21f406..00000000000
--- a/lib/gitlab/chat_commands/presenter.rb
+++ /dev/null
@@ -1,131 +0,0 @@
-module Gitlab
- module ChatCommands
- class Presenter
- include Gitlab::Routing
-
- def authorize_chat_name(url)
- message = if url
- ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})."
- else
- ":sweat_smile: Couldn't identify you, nor can I autorize you!"
- end
-
- ephemeral_response(message)
- end
-
- def help(commands, trigger)
- if commands.none?
- ephemeral_response("No commands configured")
- else
- commands.map! { |command| "#{trigger} #{command}" }
- message = header_with_list("Available commands", commands)
-
- ephemeral_response(message)
- end
- end
-
- def present(subject)
- return not_found unless subject
-
- if subject.is_a?(Gitlab::ChatCommands::Result)
- show_result(subject)
- elsif subject.respond_to?(:count)
- if subject.none?
- not_found
- elsif subject.one?
- single_resource(subject.first)
- else
- multiple_resources(subject)
- end
- else
- single_resource(subject)
- end
- end
-
- def access_denied
- ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
- end
-
- private
-
- def show_result(result)
- case result.type
- when :success
- in_channel_response(result.message)
- else
- ephemeral_response(result.message)
- end
- end
-
- def not_found
- ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:")
- end
-
- def single_resource(resource)
- return error(resource) if resource.errors.any? || !resource.persisted?
-
- message = "#{title(resource)}:"
- message << "\n\n#{resource.description}" if resource.try(:description)
-
- in_channel_response(message)
- end
-
- def multiple_resources(resources)
- titles = resources.map { |resource| title(resource) }
-
- message = header_with_list("Multiple results were found:", titles)
-
- ephemeral_response(message)
- end
-
- def error(resource)
- message = header_with_list("The action was not successful, because:", resource.errors.messages)
-
- ephemeral_response(message)
- end
-
- def title(resource)
- reference = resource.try(:to_reference) || resource.try(:id)
- title = resource.try(:title) || resource.try(:name)
-
- "[#{reference} #{title}](#{url(resource)})"
- end
-
- def header_with_list(header, items)
- message = [header]
-
- items.each do |item|
- message << "- #{item}"
- end
-
- message.join("\n")
- end
-
- def url(resource)
- url_for(
- [
- resource.project.namespace.becomes(Namespace),
- resource.project,
- resource
- ]
- )
- end
-
- def ephemeral_response(message)
- {
- response_type: :ephemeral,
- text: message,
- status: 200
- }
- end
-
- def in_channel_response(message)
- {
- response_type: :in_channel,
- text: message,
- status: 200
- }
- end
- end
- end
-end
diff --git a/lib/gitlab/chat_commands/presenters/access.rb b/lib/gitlab/chat_commands/presenters/access.rb
new file mode 100644
index 00000000000..92f4fa17f78
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/access.rb
@@ -0,0 +1,40 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Access < Presenters::Base
+ def access_denied
+ ephemeral_response(text: "Whoops! This action is not allowed. This incident will be [reported](https://xkcd.com/838/).")
+ end
+
+ def not_found
+ ephemeral_response(text: "404 not found! GitLab couldn't find what you were looking for! :boom:")
+ end
+
+ def authorize
+ message =
+ if @resource
+ ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{@resource})."
+ else
+ ":sweat_smile: Couldn't identify you, nor can I autorize you!"
+ end
+
+ ephemeral_response(text: message)
+ end
+
+ def unknown_command(commands)
+ ephemeral_response(text: help_message(trigger))
+ end
+
+ private
+
+ def help_message(trigger)
+ header_with_list("Command not found, these are the commands you can use", full_commands(trigger))
+ end
+
+ def full_commands(trigger)
+ @resource.map { |command| "#{trigger} #{command.help_message}" }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/base.rb b/lib/gitlab/chat_commands/presenters/base.rb
new file mode 100644
index 00000000000..2700a5a2ad5
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/base.rb
@@ -0,0 +1,77 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Base
+ include Gitlab::Routing.url_helpers
+
+ def initialize(resource = nil)
+ @resource = resource
+ end
+
+ def display_errors
+ message = header_with_list("The action was not successful, because:", @resource.errors.full_messages)
+
+ ephemeral_response(text: message)
+ end
+
+ private
+
+ def header_with_list(header, items)
+ message = [header]
+
+ items.each do |item|
+ message << "- #{item}"
+ end
+
+ message.join("\n")
+ end
+
+ def ephemeral_response(message)
+ response = {
+ response_type: :ephemeral,
+ status: 200
+ }.merge(message)
+
+ format_response(response)
+ end
+
+ def in_channel_response(message)
+ response = {
+ response_type: :in_channel,
+ status: 200
+ }.merge(message)
+
+ format_response(response)
+ end
+
+ def format_response(response)
+ response[:text] = format(response[:text]) if response.has_key?(:text)
+
+ if response.has_key?(:attachments)
+ response[:attachments].each do |attachment|
+ attachment[:pretext] = format(attachment[:pretext]) if attachment[:pretext]
+ attachment[:text] = format(attachment[:text]) if attachment[:text]
+ end
+ end
+
+ response
+ end
+
+ # Convert Markdown to slacks format
+ def format(string)
+ Slack::Notifier::LinkFormatter.format(string)
+ end
+
+ def resource_url
+ url_for(
+ [
+ @resource.project.namespace.becomes(Namespace),
+ @resource.project,
+ @resource
+ ]
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/deploy.rb b/lib/gitlab/chat_commands/presenters/deploy.rb
new file mode 100644
index 00000000000..863d0bf99ca
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/deploy.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Deploy < Presenters::Base
+ def present(from, to)
+ message = "Deployment started from #{from} to #{to}. [Follow its progress](#{resource_url})."
+
+ in_channel_response(text: message)
+ end
+
+ def no_actions
+ ephemeral_response(text: "No action found to be executed")
+ end
+
+ def too_many_actions
+ ephemeral_response(text: "Too many actions defined")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/help.rb b/lib/gitlab/chat_commands/presenters/help.rb
new file mode 100644
index 00000000000..cd47b7f4c6a
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/help.rb
@@ -0,0 +1,27 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class Help < Presenters::Base
+ def present(trigger, text)
+ ephemeral_response(text: help_message(trigger, text))
+ end
+
+ private
+
+ def help_message(trigger, text)
+ return "No commands available :thinking_face:" unless @resource.present?
+
+ if text.start_with?('help')
+ header_with_list("Available commands", full_commands(trigger))
+ else
+ header_with_list("Unknown command, these commands are available", full_commands(trigger))
+ end
+ end
+
+ def full_commands(trigger)
+ @resource.map { |command| "#{trigger} #{command.help_message}" }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issuable.rb b/lib/gitlab/chat_commands/presenters/issuable.rb
new file mode 100644
index 00000000000..dfb1c8f6616
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issuable.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ module Issuable
+ def color(issuable)
+ issuable.open? ? '#38ae67' : '#d22852'
+ end
+
+ def status_text(issuable)
+ issuable.open? ? 'Open' : 'Closed'
+ end
+
+ def project
+ @resource.project
+ end
+
+ def author
+ @resource.author
+ end
+
+ def fields
+ [
+ {
+ title: "Assignee",
+ value: @resource.assignee ? @resource.assignee.name : "_None_",
+ short: true
+ },
+ {
+ title: "Milestone",
+ value: @resource.milestone ? @resource.milestone.title : "_None_",
+ short: true
+ },
+ {
+ title: "Labels",
+ value: @resource.labels.any? ? @resource.label_names : "_None_",
+ short: true
+ }
+ ]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_new.rb b/lib/gitlab/chat_commands/presenters/issue_new.rb
new file mode 100644
index 00000000000..a1a3add56c9
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_new.rb
@@ -0,0 +1,50 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueNew < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ in_channel_response(new_issue)
+ end
+
+ private
+
+ def new_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "New issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :title,
+ :pretext,
+ :text,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def pretext
+ "I created an issue on #{author_profile_link}'s behalf: **#{@resource.to_reference}** in #{project_link}"
+ end
+
+ def project_link
+ "[#{project.name_with_namespace}](#{projects_url(project)})"
+ end
+
+ def author_profile_link
+ "[#{author.to_reference}](#{url_for(author)})"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_search.rb b/lib/gitlab/chat_commands/presenters/issue_search.rb
new file mode 100644
index 00000000000..3478359b91d
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_search.rb
@@ -0,0 +1,47 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueSearch < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ text = if @resource.count >= 5
+ "Here are the first 5 issues I found:"
+ elsif @resource.one?
+ "Here is the only issue I found:"
+ else
+ "Here are the #{@resource.count} issues I found:"
+ end
+
+ ephemeral_response(text: text, attachments: attachments)
+ end
+
+ private
+
+ def attachments
+ @resource.map do |issue|
+ url = "[#{issue.to_reference}](#{url_for([namespace, project, issue])})"
+
+ {
+ color: color(issue),
+ fallback: "#{issue.to_reference} #{issue.title}",
+ text: "#{url} · #{issue.title} (#{status_text(issue)})",
+
+ mrkdwn_in: [
+ :text
+ ]
+ }
+ end
+ end
+
+ def project
+ @project ||= @resource.first.project
+ end
+
+ def namespace
+ @namespace ||= project.namespace.becomes(Namespace)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/chat_commands/presenters/issue_show.rb b/lib/gitlab/chat_commands/presenters/issue_show.rb
new file mode 100644
index 00000000000..fe5847ccd15
--- /dev/null
+++ b/lib/gitlab/chat_commands/presenters/issue_show.rb
@@ -0,0 +1,61 @@
+module Gitlab
+ module ChatCommands
+ module Presenters
+ class IssueShow < Presenters::Base
+ include Presenters::Issuable
+
+ def present
+ if @resource.confidential?
+ ephemeral_response(show_issue)
+ else
+ in_channel_response(show_issue)
+ end
+ end
+
+ private
+
+ def show_issue
+ {
+ attachments: [
+ {
+ title: "#{@resource.title} · #{@resource.to_reference}",
+ title_link: resource_url,
+ author_name: author.name,
+ author_icon: author.avatar_url,
+ fallback: "Issue #{@resource.to_reference}: #{@resource.title}",
+ pretext: pretext,
+ text: text,
+ color: color(@resource),
+ fields: fields,
+ mrkdwn_in: [
+ :pretext,
+ :text,
+ :fields
+ ]
+ }
+ ]
+ }
+ end
+
+ def text
+ message = "**#{status_text(@resource)}**"
+
+ if @resource.upvotes.zero? && @resource.downvotes.zero? && @resource.user_notes_count.zero?
+ return message
+ end
+
+ message << " · "
+ message << ":+1: #{@resource.upvotes} " unless @resource.upvotes.zero?
+ message << ":-1: #{@resource.downvotes} " unless @resource.downvotes.zero?
+ message << ":speech_balloon: #{@resource.user_notes_count}" unless @resource.user_notes_count.zero?
+
+ message
+ end
+
+ def pretext
+ "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/coverage.rb b/lib/gitlab/ci/config/entry/coverage.rb
new file mode 100644
index 00000000000..12a063059cb
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/coverage.rb
@@ -0,0 +1,22 @@
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ ##
+ # Entry that represents Coverage settings.
+ #
+ class Coverage < Node
+ include Validatable
+
+ validations do
+ validates :config, regexp: true
+ end
+
+ def value
+ @config[1...-1]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/global.rb b/lib/gitlab/ci/config/entry/global.rb
index a4ec8f0ff2f..ede97cc0504 100644
--- a/lib/gitlab/ci/config/entry/global.rb
+++ b/lib/gitlab/ci/config/entry/global.rb
@@ -33,8 +33,11 @@ module Gitlab
entry :cache, Entry::Cache,
description: 'Configure caching between build jobs.'
+ entry :coverage, Entry::Coverage,
+ description: 'Coverage configuration for this pipeline.'
+
helpers :before_script, :image, :services, :after_script,
- :variables, :stages, :types, :cache, :jobs
+ :variables, :stages, :types, :cache, :coverage, :jobs
def compose!(_deps = nil)
super(self) do
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index a55362f0b6b..69a5e6f433d 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,7 @@ module Gitlab
ALLOWED_KEYS = %i[tags script only except type image services allow_failure
type stage when artifacts cache dependencies before_script
- after_script variables environment]
+ after_script variables environment coverage]
validations do
validates :config, allowed_keys: ALLOWED_KEYS
@@ -71,9 +71,12 @@ module Gitlab
entry :environment, Entry::Environment,
description: 'Environment configuration for this job.'
+ entry :coverage, Entry::Coverage,
+ description: 'Coverage configuration for this job.'
+
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
- :artifacts, :commands, :environment
+ :artifacts, :commands, :environment, :coverage
attributes :script, :tags, :allow_failure, :when, :dependencies
@@ -130,6 +133,7 @@ module Gitlab
variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
+ coverage: coverage_defined? ? coverage_value : nil,
artifacts: artifacts_value,
after_script: after_script_value }
end
diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
index f01975aab5c..9b9a0a8125a 100644
--- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
+++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb
@@ -28,17 +28,21 @@ module Gitlab
value.is_a?(String) || value.is_a?(Symbol)
end
+ def validate_regexp(value)
+ !value.nil? && Regexp.new(value.to_s) && true
+ rescue RegexpError, TypeError
+ false
+ end
+
def validate_string_or_regexp(value)
return true if value.is_a?(Symbol)
return false unless value.is_a?(String)
if value.first == '/' && value.last == '/'
- Regexp.new(value[1...-1])
+ validate_regexp(value[1...-1])
else
true
end
- rescue RegexpError
- false
end
def validate_boolean(value)
diff --git a/lib/gitlab/ci/config/entry/trigger.rb b/lib/gitlab/ci/config/entry/trigger.rb
index 28b0a9ffe01..16b234e6c59 100644
--- a/lib/gitlab/ci/config/entry/trigger.rb
+++ b/lib/gitlab/ci/config/entry/trigger.rb
@@ -9,15 +9,7 @@ module Gitlab
include Validatable
validations do
- include LegacyValidationHelpers
-
- validate :array_of_strings_or_regexps
-
- def array_of_strings_or_regexps
- unless validate_array_of_strings_or_regexps(config)
- errors.add(:config, 'should be an array of strings or regexps')
- end
- end
+ validates :config, array_of_strings_or_regexps: true
end
end
end
diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb
index 8632dd0e233..bd7428b1272 100644
--- a/lib/gitlab/ci/config/entry/validators.rb
+++ b/lib/gitlab/ci/config/entry/validators.rb
@@ -54,6 +54,51 @@ module Gitlab
end
end
+ class RegexpValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless validate_regexp(value)
+ record.errors.add(attribute, 'must be a regular expression')
+ end
+ end
+
+ private
+
+ def look_like_regexp?(value)
+ value.is_a?(String) && value.start_with?('/') &&
+ value.end_with?('/')
+ end
+
+ def validate_regexp(value)
+ look_like_regexp?(value) &&
+ Regexp.new(value.to_s[1...-1]) &&
+ true
+ rescue RegexpError
+ false
+ end
+ end
+
+ class ArrayOfStringsOrRegexpsValidator < RegexpValidator
+ def validate_each(record, attribute, value)
+ unless validate_array_of_strings_or_regexps(value)
+ record.errors.add(attribute, 'should be an array of strings or regexps')
+ end
+ end
+
+ private
+
+ def validate_array_of_strings_or_regexps(values)
+ values.is_a?(Array) && values.all?(&method(:validate_string_or_regexp))
+ end
+
+ def validate_string_or_regexp(value)
+ return false unless value.is_a?(String)
+ return validate_regexp(value) if look_like_regexp?(value)
+ true
+ end
+ end
+
class TypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
type = options[:with]
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index 2405b94db50..a09577ae48d 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -41,6 +41,8 @@ module Gitlab
end
def ensure_default_member!
+ @project.project_members.destroy_all
+
ProjectMember.create!(user: @user, access_level: ProjectMember::MASTER, source_id: @project.id, importing: true)
end
diff --git a/spec/controllers/projects/snippets_controller_spec.rb b/spec/controllers/projects/snippets_controller_spec.rb
index 32b0e42c3cd..19e948d8fb8 100644
--- a/spec/controllers/projects/snippets_controller_spec.rb
+++ b/spec/controllers/projects/snippets_controller_spec.rb
@@ -6,8 +6,8 @@ describe Projects::SnippetsController do
let(:user2) { create(:user) }
before do
- project.team << [user, :master]
- project.team << [user2, :master]
+ project.add_master(user)
+ project.add_master(user2)
end
describe 'GET #index' do
@@ -69,6 +69,86 @@ describe Projects::SnippetsController do
end
end
+ describe 'POST #create' do
+ def create_snippet(project, snippet_params = {})
+ sign_in(user)
+
+ project.add_developer(user)
+
+ post :create, {
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ project_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the project is private' do
+ let(:private_project) { create(:project_empty_repo, :private) }
+
+ context 'when the snippet is public' do
+ it 'creates the snippet' do
+ expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+ end
+
+ context 'when the project is public' do
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
+ end
+
+ describe 'POST #mark_as_spam' do
+ let(:snippet) { create(:project_snippet, :private, project: project, author: user) }
+
+ before do
+ allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+ stub_application_setting(akismet_enabled: true)
+ end
+
+ def mark_as_spam
+ admin = create(:admin)
+ create(:user_agent_detail, subject: snippet)
+ project.add_master(admin)
+ sign_in(admin)
+
+ post :mark_as_spam,
+ namespace_id: project.namespace.path,
+ project_id: project.path,
+ id: snippet.id
+ end
+
+ it 'updates the snippet' do
+ mark_as_spam
+
+ expect(snippet.reload).not_to be_submittable_as_spam
+ end
+ end
+
%w[show raw].each do |action|
describe "GET ##{action}" do
context 'when the project snippet is private' do
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
index d76fe9f580f..dadcb90cfc2 100644
--- a/spec/controllers/snippets_controller_spec.rb
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -138,6 +138,65 @@ describe SnippetsController do
end
end
+ describe 'POST #create' do
+ def create_snippet(snippet_params = {})
+ sign_in(user)
+
+ post :create, {
+ personal_snippet: { title: 'Title', content: 'Content' }.merge(snippet_params)
+ }
+ end
+
+ context 'when the snippet is spam' do
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to render_template(:new)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
+
+ describe 'POST #mark_as_spam' do
+ let(:snippet) { create(:personal_snippet, :public, author: user) }
+
+ before do
+ allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
+ stub_application_setting(akismet_enabled: true)
+ end
+
+ def mark_as_spam
+ admin = create(:admin)
+ create(:user_agent_detail, subject: snippet)
+ sign_in(admin)
+
+ post :mark_as_spam, id: snippet.id
+ end
+
+ it 'updates the snippet' do
+ mark_as_spam
+
+ expect(snippet.reload).not_to be_submittable_as_spam
+ end
+ end
+
%w(raw download).each do |action|
describe "GET #{action}" do
context 'when the personal snippet is private' do
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
new file mode 100644
index 00000000000..d9be4e5dbdd
--- /dev/null
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+feature 'Dashboard shortcuts', feature: true, js: true do
+ before do
+ login_as :user
+ visit dashboard_projects_path
+ end
+
+ scenario 'Navigate to tabs' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('p')
+
+ ensure_active_main_tab('Projects')
+
+ find('body').native.send_key('g')
+ find('body').native.send_key('i')
+
+ ensure_active_main_tab('Issues')
+
+ find('body').native.send_key('g')
+ find('body').native.send_key('m')
+
+ ensure_active_main_tab('Merge Requests')
+ end
+
+ def ensure_active_main_tab(content)
+ expect(find('.nav-sidebar li.active')).to have_content(content)
+ end
+end
diff --git a/spec/features/environment_spec.rb b/spec/features/environment_spec.rb
index 56f6cd2e095..511c95b758f 100644
--- a/spec/features/environment_spec.rb
+++ b/spec/features/environment_spec.rb
@@ -19,6 +19,10 @@ feature 'Environment', :feature do
visit_environment(environment)
end
+ scenario 'shows environment name' do
+ expect(page).to have_content(environment.name)
+ end
+
context 'without deployments' do
scenario 'does show no deployments' do
expect(page).to have_content('You don\'t have any deployments right now.')
diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb
index 72b984cfab8..c033b693213 100644
--- a/spec/features/environments_spec.rb
+++ b/spec/features/environments_spec.rb
@@ -194,7 +194,7 @@ feature 'Environments page', :feature, :js do
end
scenario 'does create a new pipeline' do
- expect(page).to have_content('Production')
+ expect(page).to have_content('production')
end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 31156fcf994..93139dc9e94 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -2,7 +2,7 @@ require 'rails_helper'
feature 'GFM autocomplete', feature: true, js: true do
include WaitForAjax
- let(:user) { create(:user, username: 'someone.special') }
+ let(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') }
let(:project) { create(:project) }
let(:label) { create(:label, project: project, title: 'special+') }
let(:issue) { create(:issue, project: project) }
@@ -59,6 +59,19 @@ feature 'GFM autocomplete', feature: true, js: true do
expect(find('#at-view-64')).to have_selector('.cur:first-of-type')
end
+ it 'includes items for assignee dropdowns with non-ASCII characters in name' do
+ page.within '.timeline-content-form' do
+ find('#note_note').native.send_keys('')
+ find('#note_note').native.send_keys("@#{user.name[0...8]}")
+ end
+
+ expect(page).to have_selector('.atwho-container')
+
+ wait_for_ajax
+
+ expect(find('#at-view-64')).to have_content(user.name)
+ end
+
it 'selects the first item for non-assignee dropdowns if a query is entered' do
page.within '.timeline-content-form' do
find('#note_note').native.send_keys('')
diff --git a/spec/features/merge_requests/toggler_behavior_spec.rb b/spec/features/merge_requests/toggler_behavior_spec.rb
new file mode 100644
index 00000000000..44a9b545ff8
--- /dev/null
+++ b/spec/features/merge_requests/toggler_behavior_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+feature 'toggler_behavior', js: true, feature: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:merge_request) { create(:merge_request, source_project: project, author: user) }
+ let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
+ let(:fragment_id) { "#note_#{note.id}" }
+
+ before do
+ login_as :admin
+ project = merge_request.source_project
+ visit "#{namespace_project_merge_request_path(project.namespace, project, merge_request)}#{fragment_id}"
+ page.current_window.resize_to(1000, 300)
+ end
+
+ describe 'scroll position' do
+ it 'should be scrolled down to fragment' do
+ page_height = page.current_window.size[1]
+ page_scroll_y = page.evaluate_script("window.scrollY")
+ fragment_position_top = page.evaluate_script("$('#{fragment_id}').offset().top")
+ expect(find('.js-toggle-content').visible?).to eq true
+ expect(find(fragment_id).visible?).to eq true
+ expect(fragment_position_top).to be >= page_scroll_y
+ expect(fragment_position_top).to be < (page_scroll_y + page_height)
+ end
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index b785b2f7704..fab2d532e06 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -89,7 +89,7 @@ describe 'Comments', feature: true do
end
end
- it 'should reset the edit note form textarea with the original content of the note if cancelled' do
+ it 'resets the edit note form textarea with the original content of the note if cancelled' do
within('.current-note-edit-form') do
fill_in 'note[note]', with: 'Some new content'
find('.btn-cancel').click
@@ -198,7 +198,7 @@ describe 'Comments', feature: true do
end
describe 'the note form' do
- it "shouldn't add a second form for same row" do
+ it "does not add a second form for same row" do
click_diff_line
is_expected.
@@ -206,7 +206,7 @@ describe 'Comments', feature: true do
count: 1)
end
- it 'should be removed when canceled' do
+ it 'is removed when canceled' do
is_expected.to have_css('.js-temp-notes-holder')
page.within("form[data-line-code='#{line_code}']") do
diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb
index 468bcc7badc..eae097126ce 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -134,7 +134,7 @@ describe DiffHelper do
let(:new_pos) { 50 }
let(:text) { 'some_text' }
- it "should generate foldable top match line for inline view with empty text by default" do
+ it "generates foldable top match line for inline view with empty text by default" do
output = diff_match_line old_pos, new_pos
expect(output).to be_html_safe
@@ -143,7 +143,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: ''
end
- it "should allow to define text and bottom option" do
+ it "allows to define text and bottom option" do
output = diff_match_line old_pos, new_pos, text: text, bottom: true
expect(output).to be_html_safe
@@ -152,7 +152,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text
end
- it "should generate match line for parallel view" do
+ it "generates match line for parallel view" do
output = diff_match_line old_pos, new_pos, text: text, view: :parallel
expect(output).to be_html_safe
@@ -162,7 +162,7 @@ describe DiffHelper do
expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text
end
- it "should allow to generate only left match line for parallel view" do
+ it "allows to generate only left match line for parallel view" do
output = diff_match_line old_pos, nil, text: text, view: :parallel
expect(output).to be_html_safe
@@ -171,7 +171,7 @@ describe DiffHelper do
expect(output).not_to have_css 'td:nth-child(3)'
end
- it "should allow to generate only right match line for parallel view" do
+ it "allows to generate only right match line for parallel view" do
output = diff_match_line nil, new_pos, text: text, view: :parallel
expect(output).to be_html_safe
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index dcaf26c186f..001cd8d6325 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -111,7 +111,7 @@ require('./fixtures/emoji_menu');
});
});
describe('::getAwardUrl', function() {
- return it('should return the url for request', function() {
+ return it('returns the url for request', function() {
return expect(awardsHandler.getAwardUrl()).toBe('http://test.host/frontend-fixtures/issues-project/issues/1/toggle_award_emoji');
});
});
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
new file mode 100644
index 00000000000..10a316f31b4
--- /dev/null
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
@@ -0,0 +1,40 @@
+require('~/filtered_search/dropdown_utils');
+require('~/filtered_search/filtered_search_tokenizer');
+require('~/filtered_search/filtered_search_dropdown');
+require('~/filtered_search/dropdown_user');
+
+(() => {
+ describe('Dropdown User', () => {
+ describe('getSearchInput', () => {
+ let dropdownUser;
+
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchDropdown.prototype, 'constructor').and.callFake(() => {});
+ spyOn(gl.DropdownUser.prototype, 'getProjectId').and.callFake(() => {});
+ spyOn(gl.DropdownUtils, 'getSearchInput').and.callFake(() => {});
+
+ dropdownUser = new gl.DropdownUser();
+ });
+
+ it('should not return the double quote found in value', () => {
+ spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+ lastToken: {
+ value: '"johnny appleseed',
+ },
+ });
+
+ expect(dropdownUser.getSearchInput()).toBe('johnny appleseed');
+ });
+
+ it('should not return the single quote found in value', () => {
+ spyOn(gl.FilteredSearchTokenizer, 'processTokens').and.returnValue({
+ lastToken: {
+ value: '\'larry boy',
+ },
+ });
+
+ expect(dropdownUser.getSearchInput()).toBe('larry boy');
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
index 1a06b60b2c2..ab6d2010d65 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
@@ -22,6 +22,7 @@ require('~/filtered_search/filtered_search_manager');
`);
spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {});
+ spyOn(gl.FilteredSearchManager.prototype, 'cleanup').and.callFake(() => {});
spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {});
spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {});
spyOn(gl.utils, 'getParameterByName').and.returnValue(null);
diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js
index 001850a4b9c..8cefdd2409d 100644
--- a/spec/javascripts/merge_request_widget_spec.js
+++ b/spec/javascripts/merge_request_widget_spec.js
@@ -1,6 +1,7 @@
/* eslint-disable space-before-function-paren, quotes, comma-dangle, dot-notation, quote-props, no-var, max-len */
require('~/merge_request_widget');
+require('~/smart_interval');
require('~/lib/utils/datetime_utility');
(function() {
@@ -21,7 +22,11 @@ require('~/lib/utils/datetime_utility');
normal: "Build {{status}}"
},
gitlab_icon: "gitlab_logo.png",
- builds_path: "http://sampledomain.local/sampleBuildsPath"
+ ci_pipeline: 80,
+ ci_sha: "12a34bc5",
+ builds_path: "http://sampledomain.local/sampleBuildsPath",
+ commits_path: "http://sampledomain.local/commits",
+ pipeline_path: "http://sampledomain.local/pipelines"
};
this["class"] = new window.gl.MergeRequestWidget(this.opts);
});
@@ -118,10 +123,11 @@ require('~/lib/utils/datetime_utility');
});
});
- return describe('getCIStatus', function() {
+ describe('getCIStatus', function() {
beforeEach(function() {
this.ciStatusData = {
"title": "Sample MR title",
+ "pipeline": 80,
"sha": "12a34bc5",
"status": "success",
"coverage": 98
@@ -165,6 +171,22 @@ require('~/lib/utils/datetime_utility');
this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled();
});
+ it('should update the pipeline URL when the pipeline changes', function() {
+ var spy;
+ spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
+ this["class"].getCIStatus(false);
+ this.ciStatusData.pipeline += 1;
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalled();
+ });
+ it('should update the commit URL when the sha changes', function() {
+ var spy;
+ spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
+ this["class"].getCIStatus(false);
+ this.ciStatusData.sha = "9b50b99a";
+ this["class"].getCIStatus(false);
+ return expect(spy).toHaveBeenCalled();
+ });
});
});
}).call(this);
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 331f36ccc44..602ac01aec3 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -11,9 +11,9 @@ require('~/shortcuts_issuable');
beforeEach(function() {
loadFixtures(fixtureName);
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
- return this.shortcut = new ShortcutsIssuable();
+ this.shortcut = new ShortcutsIssuable();
});
- return describe('#replyWithSelectedText', function() {
+ describe('#replyWithSelectedText', function() {
var stubSelection;
// Stub window.gl.utils.getSelectedFragment to return a node with the provided HTML.
stubSelection = function(html) {
@@ -24,51 +24,57 @@ require('~/shortcuts_issuable');
};
};
beforeEach(function() {
- return this.selector = 'form.js-main-target-form textarea#note_note';
+ this.selector = 'form.js-main-target-form textarea#note_note';
});
describe('with empty selection', function() {
- return it('does nothing', function() {
- stubSelection('');
+ it('does not return an error', function() {
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe('');
+ expect($(this.selector).val()).toBe('');
+ });
+ it('triggers `input`', function() {
+ var focused = false;
+ $(this.selector).on('focus', function() {
+ focused = true;
+ });
+ this.shortcut.replyWithSelectedText();
+ expect(focused).toBe(true);
});
});
describe('with any selection', function() {
beforeEach(function() {
- return stubSelection('<p>Selected text.</p>');
+ stubSelection('<p>Selected text.</p>');
});
it('leaves existing input intact', function() {
$(this.selector).val('This text was already here.');
expect($(this.selector).val()).toBe('This text was already here.');
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
+ expect($(this.selector).val()).toBe("This text was already here.\n\n> Selected text.\n\n");
});
it('triggers `input`', function() {
- var triggered;
- triggered = false;
+ var triggered = false;
$(this.selector).on('input', function() {
- return triggered = true;
+ triggered = true;
});
this.shortcut.replyWithSelectedText();
- return expect(triggered).toBe(true);
+ expect(triggered).toBe(true);
});
- return it('triggers `focus`', function() {
+ it('triggers `focus`', function() {
this.shortcut.replyWithSelectedText();
expect(document.activeElement).toBe(document.querySelector(this.selector));
});
});
describe('with a one-line selection', function() {
- return it('quotes the selection', function() {
+ it('quotes the selection', function() {
stubSelection('<p>This text has been selected.</p>');
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
+ expect($(this.selector).val()).toBe("> This text has been selected.\n\n");
});
});
- return describe('with a multi-line selection', function() {
- return it('quotes the selected lines as a group', function() {
+ describe('with a multi-line selection', function() {
+ it('quotes the selected lines as a group', function() {
stubSelection("<p>Selected line one.</p>\n\n<p>Selected line two.</p>\n\n<p>Selected line three.</p>");
this.shortcut.replyWithSelectedText();
- return expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
+ expect($(this.selector).val()).toBe("> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n");
});
});
});
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index f824e2e1efe..49349035b3b 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -4,6 +4,33 @@ module Ci
describe GitlabCiYamlProcessor, lib: true do
let(:path) { 'path' }
+ describe '#build_attributes' do
+ context 'Coverage entry' do
+ subject { described_class.new(config, path).build_attributes(:rspec) }
+
+ let(:config_base) { { rspec: { script: "rspec" } } }
+ let(:config) { YAML.dump(config_base) }
+
+ context 'when config has coverage set at the global scope' do
+ before do
+ config_base.update(coverage: '/\(\d+\.\d+\) covered/')
+ end
+
+ context "and 'rspec' job doesn't have coverage set" do
+ it { is_expected.to include(coverage_regex: '\(\d+\.\d+\) covered') }
+ end
+
+ context "but 'rspec' job also has coverage set" do
+ before do
+ config_base[:rspec][:coverage] = '/Code coverage: \d+\.\d+/'
+ end
+
+ it { is_expected.to include(coverage_regex: 'Code coverage: \d+\.\d+') }
+ end
+ end
+ end
+ end
+
describe "#builds_for_ref" do
let(:type) { 'test' }
@@ -21,6 +48,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {},
allow_failure: false,
@@ -435,6 +463,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
@@ -463,6 +492,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.5",
@@ -702,6 +732,7 @@ module Ci
stage_idx: 1,
name: "rspec",
commands: "pwd\nrspec",
+ coverage_regex: nil,
tag_list: [],
options: {
image: "ruby:2.1",
@@ -913,6 +944,7 @@ module Ci
stage_idx: 1,
name: "normal_job",
commands: "test",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
@@ -958,6 +990,7 @@ module Ci
stage_idx: 0,
name: "job1",
commands: "execute-script-for-job",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
@@ -970,6 +1003,7 @@ module Ci
stage_idx: 0,
name: "job2",
commands: "execute-script-for-job",
+ coverage_regex: nil,
tag_list: [],
options: {},
when: "on_success",
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index f251c0dd25a..b234de4c772 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -58,58 +58,102 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
- it 'recognizes user lfs tokens' do
- user = create(:user)
- token = Gitlab::LfsToken.new(user).token
+ context 'while using LFS authenticate' do
+ it 'recognizes user lfs tokens' do
+ user = create(:user)
+ token = Gitlab::LfsToken.new(user).token
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
- expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
- end
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.username)
+ expect(gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :lfs_token, full_authentication_abilities))
+ end
- it 'recognizes deploy key lfs tokens' do
- key = create(:deploy_key)
- token = Gitlab::LfsToken.new(key).token
+ it 'recognizes deploy key lfs tokens' do
+ key = create(:deploy_key)
+ token = Gitlab::LfsToken.new(key).token
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
- expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
- end
+ expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: "lfs+deploy-key-#{key.id}")
+ expect(gl_auth.find_for_git_client("lfs+deploy-key-#{key.id}", token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(key, nil, :lfs_deploy_token, read_authentication_abilities))
+ end
- context "while using OAuth tokens as passwords" do
- it 'succeeds for OAuth tokens with the `api` scope' do
+ it 'does not try password auth before oauth' do
user = create(:user)
- application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
- token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
+ token = Gitlab::LfsToken.new(user).token
+
+ expect(gl_auth).not_to receive(:find_with_user_password)
+ gl_auth.find_for_git_client(user.username, token, project: nil, ip: 'ip')
+ end
+ end
+
+ context 'while using OAuth tokens as passwords' do
+ let(:user) { create(:user) }
+ let(:token_w_api_scope) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api') }
+ let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
+
+ it 'succeeds for OAuth tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'oauth2')
- expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
+ expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities))
end
it 'fails for OAuth tokens with other scopes' do
- user = create(:user)
- application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
- token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "read_user")
+ token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'read_user')
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'oauth2')
expect(gl_auth.find_for_git_client("oauth2", token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
+
+ it 'does not try password auth before oauth' do
+ expect(gl_auth).not_to receive(:find_with_user_password)
+
+ gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
+ end
end
- context "while using personal access tokens as passwords" do
- it 'succeeds for personal access tokens with the `api` scope' do
- user = create(:user)
- personal_access_token = create(:personal_access_token, user: user, scopes: ['api'])
+ context 'while using personal access tokens as passwords' do
+ let(:user) { create(:user) }
+ let(:token_w_api_scope) { create(:personal_access_token, user: user, scopes: ['api']) }
+ it 'succeeds for personal access tokens with the `api` scope' do
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: user.email)
- expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
+ expect(gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities))
end
it 'fails for personal access tokens with other scopes' do
- user = create(:user)
personal_access_token = create(:personal_access_token, user: user, scopes: ['read_user'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: user.email)
expect(gl_auth.find_for_git_client(user.email, personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(nil, nil))
end
+
+ it 'does not try password auth before personal access tokens' do
+ expect(gl_auth).not_to receive(:find_with_user_password)
+
+ gl_auth.find_for_git_client(user.email, token_w_api_scope.token, project: nil, ip: 'ip')
+ end
+ end
+
+ context 'while using regular user and password' do
+ it 'falls through lfs authentication' do
+ user = create(
+ :user,
+ username: 'normal_user',
+ password: 'my-secret',
+ )
+
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
+
+ it 'falls through oauth authentication when the username is oauth2' do
+ user = create(
+ :user,
+ username: 'oauth2',
+ password: 'my-secret',
+ )
+
+ expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
+ .to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
end
it 'returns double nil for invalid credentials' do
diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb
index 1e81eaef18c..b6e924d67be 100644
--- a/spec/lib/gitlab/chat_commands/command_spec.rb
+++ b/spec/lib/gitlab/chat_commands/command_spec.rb
@@ -24,7 +24,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'displays the help message' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Available commands')
+ expect(subject[:text]).to start_with('Unknown command')
expect(subject[:text]).to match('/gitlab issue show')
end
end
@@ -34,47 +34,7 @@ describe Gitlab::ChatCommands::Command, service: true do
it 'rejects the actions' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! That action is not allowed')
- end
- end
-
- context 'issue is successfully created' do
- let(:params) { { text: "issue create my new issue" } }
-
- before do
- project.team << [user, :master]
- end
-
- it 'presents the issue' do
- expect(subject[:text]).to match("my new issue")
- end
-
- it 'shows a link to the new issue' do
- expect(subject[:text]).to match(/\/issues\/\d+/)
- end
- end
-
- context 'searching for an issue' do
- let(:params) { { text: 'issue search find me' } }
- let!(:issue) { create(:issue, project: project, title: 'find me') }
-
- before do
- project.team << [user, :master]
- end
-
- context 'a single issue is found' do
- it 'presents the issue' do
- expect(subject[:text]).to match(issue.title)
- end
- end
-
- context 'multiple issues found' do
- let!(:issue2) { create(:issue, project: project, title: "someone find me") }
-
- it 'shows a link to the new issue' do
- expect(subject[:text]).to match(issue.title)
- expect(subject[:text]).to match(issue2.title)
- end
+ expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end
end
@@ -90,7 +50,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'and user can not create deployment' do
it 'returns action' do
expect(subject[:response_type]).to be(:ephemeral)
- expect(subject[:text]).to start_with('Whoops! That action is not allowed')
+ expect(subject[:text]).to start_with('Whoops! This action is not allowed')
end
end
@@ -100,7 +60,7 @@ describe Gitlab::ChatCommands::Command, service: true do
end
it 'returns action' do
- expect(subject[:text]).to include('Deployment from staging to production started.')
+ expect(subject[:text]).to include('Deployment started from staging to production')
expect(subject[:response_type]).to be(:in_channel)
end
@@ -130,7 +90,7 @@ describe Gitlab::ChatCommands::Command, service: true do
context 'IssueCreate is triggered' do
let(:params) { { text: 'issue create my title' } }
- it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) }
+ it { is_expected.to eq(Gitlab::ChatCommands::IssueNew) }
end
context 'IssueSearch is triggered' do
diff --git a/spec/lib/gitlab/chat_commands/deploy_spec.rb b/spec/lib/gitlab/chat_commands/deploy_spec.rb
index bd8099c92da..b3358a32161 100644
--- a/spec/lib/gitlab/chat_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/chat_commands/deploy_spec.rb
@@ -15,8 +15,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
context 'if no environment is defined' do
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'does not execute an action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
end
end
@@ -26,8 +27,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
let!(:deployment) { create(:deployment, environment: staging, deployable: build) }
context 'without actions' do
- it 'returns nil' do
- expect(subject).to be_nil
+ it 'does not execute an action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
end
end
@@ -37,8 +39,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
it 'returns success result' do
- expect(subject.type).to eq(:success)
- expect(subject.message).to include('Deployment from staging to production started')
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with('Deployment started from staging to production')
end
context 'when duplicate action exists' do
@@ -47,8 +49,8 @@ describe Gitlab::ChatCommands::Deploy, service: true do
end
it 'returns error' do
- expect(subject.type).to eq(:error)
- expect(subject.message).to include('Too many actions defined')
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq('Too many actions defined')
end
end
@@ -59,9 +61,9 @@ describe Gitlab::ChatCommands::Deploy, service: true do
name: 'teardown', environment: 'production')
end
- it 'returns success result' do
- expect(subject.type).to eq(:success)
- expect(subject.message).to include('Deployment from staging to production started')
+ it 'returns the success message' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with('Deployment started from staging to production')
end
end
end
diff --git a/spec/lib/gitlab/chat_commands/issue_create_spec.rb b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
index 6c71e79ff6d..84c22328064 100644
--- a/spec/lib/gitlab/chat_commands/issue_create_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_new_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ChatCommands::IssueCreate, service: true do
+describe Gitlab::ChatCommands::IssueNew, service: true do
describe '#execute' do
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
@@ -18,7 +18,7 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
it 'creates the issue' do
expect { subject }.to change { project.issues.count }.by(1)
- expect(subject.title).to eq('bird is the word')
+ expect(subject[:response_type]).to be(:in_channel)
end
end
@@ -41,6 +41,16 @@ describe Gitlab::ChatCommands::IssueCreate, service: true do
expect { subject }.to change { project.issues.count }.by(1)
end
end
+
+ context 'issue cannot be created' do
+ let!(:issue) { create(:issue, project: project, title: 'bird is the word') }
+ let(:regex_match) { described_class.match("issue create #{'a' * 512}}") }
+
+ it 'displays the errors' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("- Title is too long")
+ end
+ end
end
describe '.match' do
diff --git a/spec/lib/gitlab/chat_commands/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
index 24c06a967fa..551ccb79a58 100644
--- a/spec/lib/gitlab/chat_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_search_spec.rb
@@ -2,9 +2,9 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueSearch, service: true do
describe '#execute' do
- let!(:issue) { create(:issue, title: 'find me') }
+ let!(:issue) { create(:issue, project: project, title: 'find me') }
let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') }
- let(:project) { issue.project }
+ let(:project) { create(:empty_project) }
let(:user) { issue.author }
let(:regex_match) { described_class.match("issue search find") }
@@ -14,7 +14,8 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
context 'when the user has no access' do
it 'only returns the open issues' do
- expect(subject).not_to include(confidential)
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
end
end
@@ -24,13 +25,14 @@ describe Gitlab::ChatCommands::IssueSearch, service: true do
end
it 'returns all results' do
- expect(subject).to include(confidential, issue)
+ expect(subject).to have_key(:attachments)
+ expect(subject[:text]).to eq("Here are the 2 issues I found:")
end
end
context 'without hits on the query' do
it 'returns an empty collection' do
- expect(subject).to be_empty
+ expect(subject[:text]).to match("not found")
end
end
end
diff --git a/spec/lib/gitlab/chat_commands/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
index 2eab73e49e5..1f20d0a44ce 100644
--- a/spec/lib/gitlab/chat_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/chat_commands/issue_show_spec.rb
@@ -2,8 +2,8 @@ require 'spec_helper'
describe Gitlab::ChatCommands::IssueShow, service: true do
describe '#execute' do
- let(:issue) { create(:issue) }
- let(:project) { issue.project }
+ let(:issue) { create(:issue, project: project) }
+ let(:project) { create(:empty_project) }
let(:user) { issue.author }
let(:regex_match) { described_class.match("issue show #{issue.iid}") }
@@ -16,15 +16,19 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
end
context 'the issue exists' do
+ let(:title) { subject[:attachments].first[:title] }
+
it 'returns the issue' do
- expect(subject.iid).to be issue.iid
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(title).to start_with(issue.title)
end
context 'when its reference is given' do
let(:regex_match) { described_class.match("issue show #{issue.to_reference}") }
it 'shows the issue' do
- expect(subject.iid).to be issue.iid
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(title).to start_with(issue.title)
end
end
end
@@ -32,17 +36,24 @@ describe Gitlab::ChatCommands::IssueShow, service: true do
context 'the issue does not exist' do
let(:regex_match) { described_class.match("issue show 2343242") }
- it "returns nil" do
- expect(subject).to be_nil
+ it "returns not found" do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to match("not found")
end
end
end
- describe 'self.match' do
+ describe '.match' do
it 'matches the iid' do
match = described_class.match("issue show 123")
expect(match[:iid]).to eq("123")
end
+
+ it 'accepts a reference' do
+ match = described_class.match("issue show #{Issue.reference_prefix}123")
+
+ expect(match[:iid]).to eq("123")
+ end
end
end
diff --git a/spec/lib/gitlab/chat_commands/presenters/access_spec.rb b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
new file mode 100644
index 00000000000..ae41d75ab0c
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/access_spec.rb
@@ -0,0 +1,49 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Access do
+ describe '#access_denied' do
+ subject { described_class.new.access_denied }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'displays an error message' do
+ expect(subject[:text]).to match("is not allowed")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ describe '#not_found' do
+ subject { described_class.new.not_found }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user the resource was not found' do
+ expect(subject[:text]).to match("not found!")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ describe '#authorize' do
+ context 'with an authorization URL' do
+ subject { described_class.new('http://authorize.me').authorize }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user to authorize' do
+ expect(subject[:text]).to match("connect your GitLab account")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+
+ context 'without authorization url' do
+ subject { described_class.new.authorize }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'tells the user to authorize' do
+ expect(subject[:text]).to match("Couldn't identify you")
+ expect(subject[:response_type]).to be(:ephemeral)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
new file mode 100644
index 00000000000..dc2dd300072
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/deploy_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::Deploy do
+ let(:build) { create(:ci_build) }
+
+ describe '#present' do
+ subject { described_class.new(build).present('staging', 'prod') }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'messages the channel of the deploy' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject[:text]).to start_with("Deployment started from staging to prod")
+ end
+ end
+
+ describe '#no_actions' do
+ subject { described_class.new(nil).no_actions }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'tells the user there is no action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("No action found to be executed")
+ end
+ end
+
+ describe '#too_many_actions' do
+ subject { described_class.new([]).too_many_actions }
+
+ it { is_expected.to have_key(:text) }
+ it { is_expected.to have_key(:response_type) }
+ it { is_expected.to have_key(:status) }
+ it { is_expected.not_to have_key(:attachments) }
+
+ it 'tells the user there is no action' do
+ expect(subject[:response_type]).to be(:ephemeral)
+ expect(subject[:text]).to eq("Too many actions defined")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
new file mode 100644
index 00000000000..17fcdbc2452
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_new_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueNew do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
new file mode 100644
index 00000000000..ec6d3e34a96
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_search_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueSearch do
+ let(:project) { create(:empty_project) }
+ let(:message) { subject[:text] }
+
+ before { create_list(:issue, 2, project: project) }
+
+ subject { described_class.new(project.issues).present }
+
+ it 'formats the message correct' do
+ is_expected.to have_key(:text)
+ is_expected.to have_key(:status)
+ is_expected.to have_key(:response_type)
+ is_expected.to have_key(:attachments)
+ end
+
+ it 'shows a list of results' do
+ expect(subject[:response_type]).to be(:ephemeral)
+
+ expect(message).to start_with("Here are the 2 issues I found")
+ end
+end
diff --git a/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
new file mode 100644
index 00000000000..5b678d31fce
--- /dev/null
+++ b/spec/lib/gitlab/chat_commands/presenters/issue_show_spec.rb
@@ -0,0 +1,37 @@
+require 'spec_helper'
+
+describe Gitlab::ChatCommands::Presenters::IssueShow do
+ let(:project) { create(:empty_project) }
+ let(:issue) { create(:issue, project: project) }
+ let(:attachment) { subject[:attachments].first }
+
+ subject { described_class.new(issue).present }
+
+ it { is_expected.to be_a(Hash) }
+
+ it 'shows the issue' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(subject).to have_key(:attachments)
+ expect(attachment[:title]).to start_with(issue.title)
+ end
+
+ context 'with upvotes' do
+ before do
+ create(:award_emoji, :upvote, awardable: issue)
+ end
+
+ it 'shows the upvote count' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(attachment[:text]).to start_with("**Open** · :+1: 1")
+ end
+ end
+
+ context 'confidential issue' do
+ let(:issue) { create(:issue, project: project) }
+
+ it 'shows an ephemeral response' do
+ expect(subject[:response_type]).to be(:in_channel)
+ expect(attachment[:text]).to start_with("**Open**")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
new file mode 100644
index 00000000000..4c6bd859552
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Config::Entry::Coverage do
+ let(:entry) { described_class.new(config) }
+
+ describe 'validations' do
+ context "when entry config value doesn't have the surrounding '/'" do
+ let(:config) { 'Code coverage: \d+\.\d+' }
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to include(/coverage config must be a regular expression/) }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.not_to be_valid }
+ end
+ end
+
+ context "when entry config value has the surrounding '/'" do
+ let(:config) { '/Code coverage: \d+\.\d+/' }
+
+ describe '#value' do
+ subject { entry.value }
+ it { is_expected.to eq(config[1...-1]) }
+ end
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to be_empty }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'when entry value is not valid' do
+ let(:config) { '(malformed regexp' }
+
+ describe '#errors' do
+ subject { entry.errors }
+ it { is_expected.to include(/coverage config must be a regular expression/) }
+ end
+
+ describe '#valid?' do
+ subject { entry }
+ it { is_expected.not_to be_valid }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index e64c8d46bd8..d4f1780b174 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -4,12 +4,17 @@ describe Gitlab::Ci::Config::Entry::Global do
let(:global) { described_class.new(hash) }
describe '.nodes' do
- it 'can contain global config keys' do
- expect(described_class.nodes).to include :before_script
+ it 'returns a hash' do
+ expect(described_class.nodes).to be_a(Hash)
end
- it 'returns a hash' do
- expect(described_class.nodes).to be_a Hash
+ context 'when filtering all the entry/node names' do
+ it 'contains the expected node names' do
+ node_names = described_class.nodes.keys
+ expect(node_names).to match_array(%i[before_script image services
+ after_script variables stages
+ types cache coverage])
+ end
end
end
@@ -35,7 +40,7 @@ describe Gitlab::Ci::Config::Entry::Global do
end
it 'creates node object for each entry' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'creates node object using valid class' do
@@ -176,7 +181,7 @@ describe Gitlab::Ci::Config::Entry::Global do
describe '#nodes' do
it 'instantizes all nodes' do
- expect(global.descendants.count).to eq 8
+ expect(global.descendants.count).to eq 9
end
it 'contains unspecified nodes' do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index fc9b8b86dc4..d20f4ec207d 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -3,6 +3,20 @@ require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do
let(:entry) { described_class.new(config, name: :rspec) }
+ describe '.nodes' do
+ context 'when filtering all the entry/node names' do
+ subject { described_class.nodes.keys }
+
+ let(:result) do
+ %i[before_script script stage type after_script cache
+ image services only except variables artifacts
+ environment coverage]
+ end
+
+ it { is_expected.to match_array result }
+ end
+ end
+
describe 'validations' do
before { entry.compose! }
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 1e21270d928..5893485634d 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -12,11 +12,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with a diff file" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file, repository: project.repository).highlight }
- it 'should return Gitlab::Diff::Line elements' do
+ it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end
- it 'should not modify "match" lines' do
+ it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
@@ -43,11 +43,11 @@ describe Gitlab::Diff::Highlight, lib: true do
context "with diff lines" do
let(:subject) { Gitlab::Diff::Highlight.new(diff_file.diff_lines, repository: project.repository).highlight }
- it 'should return Gitlab::Diff::Line elements' do
+ it 'returns Gitlab::Diff::Line elements' do
expect(subject.first).to be_an_instance_of(Gitlab::Diff::Line)
end
- it 'should not modify "match" lines' do
+ it 'does not modify "match" lines' do
expect(subject[0].text).to eq('@@ -6,12 +6,18 @@ module Popen')
expect(subject[22].text).to eq('@@ -19,6 +25,7 @@ module Popen')
end
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index fe5fa048413..0f779339c54 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do
subject { described_class.new(diff_file) }
describe '#parallelize' do
- it 'should return an array of arrays containing the parsed diff' do
+ it 'returns an array of arrays containing the parsed diff' do
diff_lines = diff_file.highlighted_diff_lines
expected = [
# Unchanged lines
diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb
index fadfe4d378e..e177d883158 100644
--- a/spec/lib/gitlab/highlight_spec.rb
+++ b/spec/lib/gitlab/highlight_spec.rb
@@ -12,7 +12,7 @@ describe Gitlab::Highlight, lib: true do
Gitlab::Highlight.highlight_lines(project.repository, commit.id, 'files/ruby/popen.rb')
end
- it 'should properly highlight all the lines' do
+ it 'highlights all the lines properly' do
expect(lines[4]).to eq(%Q{<span id="LC5" class="line"> <span class="kp">extend</span> <span class="nb">self</span></span>\n})
expect(lines[21]).to eq(%Q{<span id="LC22" class="line"> <span class="k">unless</span> <span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">path</span><span class="p">)</span></span>\n})
expect(lines[26]).to eq(%Q{<span id="LC27" class="line"> <span class="vi">@cmd_status</span> <span class="o">=</span> <span class="mi">0</span></span>\n})
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7fb6829f582..20241d4d63e 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -52,6 +52,7 @@ snippets:
- project
- notes
- award_emoji
+- user_agent_detail
releases:
- project
project_members:
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index 0b7984d6ca9..f2cb028206f 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -92,5 +92,29 @@ describe Gitlab::ImportExport::MembersMapper, services: true do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
+
+ context 'importer same as group member' do
+ let(:user2) { create(:admin, authorized_projects_populated: true) }
+ let(:group) { create(:group) }
+ let(:project) { create(:empty_project, :public, name: 'searchable_project', namespace: group) }
+ let(:members_mapper) do
+ described_class.new(
+ exported_members: exported_members, user: user2, project: project)
+ end
+
+ before do
+ group.add_users([user, user2], GroupMember::DEVELOPER)
+ end
+
+ it 'maps the project member' do
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+
+ it 'maps the project member if it already exists' do
+ project.add_master(user2)
+
+ expect(members_mapper.map[exported_user_id]).to eq(user2.id)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 493bc2db21a..95b230e4f5c 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -222,6 +222,7 @@ CommitStatus:
- queued_at
- token
- lock_version
+- coverage_regex
Ci::Variable:
- id
- project_id
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index b9d12c3c24c..9dd997aa7dc 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
- it 'should block user in GitLab' do
+ it 'blocks user in GitLab' do
expect(access).to receive(:block_user).with(user, 'does not exist anymore')
access.allowed?
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 47cd5075a7d..e20b394c525 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -221,6 +221,47 @@ describe Ci::Build, :models do
end
end
+ describe '#coverage_regex' do
+ subject { build.coverage_regex }
+
+ context 'when project has build_coverage_regex set' do
+ let(:project_regex) { '\(\d+\.\d+\) covered' }
+
+ before do
+ project.build_coverage_regex = project_regex
+ end
+
+ context 'and coverage_regex attribute is not set' do
+ it { is_expected.to eq(project_regex) }
+ end
+
+ context 'but coverage_regex attribute is also set' do
+ let(:build_regex) { 'Code coverage: \d+\.\d+' }
+
+ before do
+ build.coverage_regex = build_regex
+ end
+
+ it { is_expected.to eq(build_regex) }
+ end
+ end
+
+ context 'when neither project nor build has coverage regex set' do
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#update_coverage' do
+ context "regarding coverage_regex's value," do
+ it "saves the correct extracted coverage value" do
+ build.coverage_regex = '\(\d+.\d+\%\) covered'
+ allow(build).to receive(:trace) { 'Coverage 1033 / 1051 LOC (98.29%) covered' }
+ expect(build).to receive(:update_attributes).with(coverage: 98.29) { true }
+ expect(build.update_coverage).to be true
+ end
+ end
+ end
+
describe 'deployment' do
describe '#last_deployment' do
subject { build.last_deployment }
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index 3b7cc7d9e2e..9053485939e 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -27,15 +27,13 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
+ issue = create(:issue, project: project)
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(issue, message: "Closes nothing")
+ create_commit_referencing_issue(issue)
+ create_merge_request_closing_issue(issue, message: "Closes nothing")
- merge_merge_requests_closing_issue(issue)
- deploy_master
- end
+ merge_merge_requests_closing_issue(issue)
+ deploy_master
expect(subject[:code].median).to be_nil
end
@@ -60,14 +58,12 @@ describe 'CycleAnalytics#code', feature: true do
context "when a regular merge request (that doesn't close the issue) is created" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
+ issue = create(:issue, project: project)
- create_commit_referencing_issue(issue)
- create_merge_request_closing_issue(issue, message: "Closes nothing")
+ create_commit_referencing_issue(issue)
+ create_merge_request_closing_issue(issue, message: "Closes nothing")
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:code].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 5c73edbbc53..fc7d18bd40e 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -33,14 +33,12 @@ describe 'CycleAnalytics#issue', models: true do
context "when a regular label (instead of a list label) is added to the issue" do
it "returns nil" do
- 5.times do
- regular_label = create(:label)
- issue = create(:issue, project: project)
- issue.update(label_ids: [regular_label.id])
+ regular_label = create(:label)
+ issue = create(:issue, project: project)
+ issue.update(label_ids: [regular_label.id])
- create_merge_request_closing_issue(issue)
- merge_merge_requests_closing_issue(issue)
- end
+ create_merge_request_closing_issue(issue)
+ merge_merge_requests_closing_issue(issue)
expect(subject[:issue].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 591bbdddf55..2cbee741fb0 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -29,11 +29,9 @@ describe 'CycleAnalytics#production', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do
- 5.times do
- merge_request = create(:merge_request)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master
- end
+ merge_request = create(:merge_request)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master
expect(subject[:production].median).to be_nil
end
@@ -41,12 +39,10 @@ describe 'CycleAnalytics#production', feature: true do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(environment: 'staging')
- end
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master(environment: 'staging')
expect(subject[:production].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 33d2c0a7416..febb18c9884 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -23,9 +23,7 @@ describe 'CycleAnalytics#review', feature: true do
context "when a regular merge request (that doesn't close the issue) is created and merged" do
it "returns nil" do
- 5.times do
- MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
- end
+ MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
expect(subject[:review].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 00693d67475..104e65335dd 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -40,11 +40,9 @@ describe 'CycleAnalytics#staging', feature: true do
context "when a regular merge request (that doesn't close the issue) is merged and deployed" do
it "returns nil" do
- 5.times do
- merge_request = create(:merge_request)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master
- end
+ merge_request = create(:merge_request)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master
expect(subject[:staging].median).to be_nil
end
@@ -52,12 +50,10 @@ describe 'CycleAnalytics#staging', feature: true do
context "when the deployment happens to a non-production environment" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- MergeRequests::MergeService.new(project, user).execute(merge_request)
- deploy_master(environment: 'staging')
- end
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ MergeRequests::MergeService.new(project, user).execute(merge_request)
+ deploy_master(environment: 'staging')
expect(subject[:staging].median).to be_nil
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index f857ea6cbec..c2ba012a0e6 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -24,16 +24,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is for a regular merge request (that doesn't close an issue)" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.succeed!
+ pipeline.run!
+ pipeline.succeed!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
@@ -41,12 +39,10 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is not for a merge request" do
it "returns nil" do
- 5.times do
- pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/master", sha: project.repository.commit('master').sha)
- pipeline.run!
- pipeline.succeed!
- end
+ pipeline.run!
+ pipeline.succeed!
expect(subject[:test].median).to be_nil
end
@@ -54,16 +50,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is dropped (failed)" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.drop!
+ pipeline.run!
+ pipeline.drop!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
@@ -71,16 +65,14 @@ describe 'CycleAnalytics#test', feature: true do
context "when the pipeline is cancelled" do
it "returns nil" do
- 5.times do
- issue = create(:issue, project: project)
- merge_request = create_merge_request_closing_issue(issue)
- pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
+ issue = create(:issue, project: project)
+ merge_request = create_merge_request_closing_issue(issue)
+ pipeline = create(:ci_pipeline, ref: "refs/heads/#{merge_request.source_branch}", sha: merge_request.diff_head_sha)
- pipeline.run!
- pipeline.cancel!
+ pipeline.run!
+ pipeline.cancel!
- merge_merge_requests_closing_issue(issue)
- end
+ merge_merge_requests_closing_issue(issue)
expect(subject[:test].median).to be_nil
end
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 90d14c2c0b9..e4be0aba7a6 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -117,7 +117,7 @@ describe ProjectMember, models: true do
users = create_list(:user, 2)
described_class.add_users_to_projects(
- [projects.first.id, projects.second],
+ [projects.first.id, projects.second.id],
[users.first.id, users.second],
described_class::MASTER)
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb
index 645e36683bc..bd6e23ee769 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/builds_spec.rb
@@ -67,7 +67,7 @@ describe API::Builds, api: true do
context 'unauthorized user' do
let(:api_user) { nil }
- it 'should not return project builds' do
+ it 'does not return project builds' do
expect(response).to have_http_status(401)
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 1187d2e609d..a027c23bb88 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -326,7 +326,7 @@ describe API::Groups, api: true do
expect(response).to have_http_status(404)
end
- it "should only return projects to which user has access" do
+ it "only returns projects to which user has access" do
project3.team << [user3, :developer]
get api("/groups/#{group1.id}/projects", user3)
@@ -338,7 +338,7 @@ describe API::Groups, api: true do
end
context "when authenticated as admin" do
- it "should return any existing group" do
+ it "returns any existing group" do
get api("/groups/#{group2.id}/projects", admin)
expect(response).to have_http_status(200)
@@ -346,7 +346,7 @@ describe API::Groups, api: true do
expect(json_response.first['name']).to eq(project2.name)
end
- it "should not return a non existing group" do
+ it "does not return a non existing group" do
get api("/groups/1328/projects", admin)
expect(response).to have_http_status(404)
@@ -354,7 +354,7 @@ describe API::Groups, api: true do
end
context 'when using group path in URL' do
- it 'should return any existing group' do
+ it 'returns any existing group' do
get api("/groups/#{group1.path}/projects", admin)
expect(response).to have_http_status(200)
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 01032c0929b..45d5ae267c5 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -4,6 +4,7 @@ describe API::ProjectSnippets, api: true do
include ApiHelpers
let(:project) { create(:empty_project, :public) }
+ let(:user) { create(:user) }
let(:admin) { create(:admin) }
describe 'GET /projects/:project_id/snippets/:id' do
@@ -22,7 +23,7 @@ describe API::ProjectSnippets, api: true do
let(:user) { create(:user) }
it 'returns all snippets available to team member' do
- project.team << [user, :developer]
+ project.add_developer(user)
public_snippet = create(:project_snippet, :public, project: project)
internal_snippet = create(:project_snippet, :internal, project: project)
private_snippet = create(:project_snippet, :private, project: project)
@@ -50,7 +51,7 @@ describe API::ProjectSnippets, api: true do
title: 'Test Title',
file_name: 'test.rb',
code: 'puts "hello world"',
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ visibility_level: Snippet::PUBLIC
}
end
@@ -72,6 +73,51 @@ describe API::ProjectSnippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def create_snippet(project, snippet_params = {})
+ project.add_developer(user)
+
+ post api("/projects/#{project.id}/snippets", user), params.merge(snippet_params)
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the project is private' do
+ let(:private_project) { create(:project_empty_repo, :private) }
+
+ context 'when the snippet is public' do
+ it 'creates the snippet' do
+ expect { create_snippet(private_project, visibility_level: Snippet::PUBLIC) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+ end
+
+ context 'when the project is public' do
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to have_http_status(400)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(project, visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
+ end
end
describe 'PUT /projects/:project_id/snippets/:id/' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a1db81ce18c..753dde0ca3a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -459,7 +459,7 @@ describe API::Projects, api: true do
before { project }
before { admin }
- it 'should create new project without path and return 201' do
+ it 'creates new project without path and return 201' do
expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1)
expect(response).to have_http_status(201)
end
diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb
index f6fb6ea5506..6b9a739b439 100644
--- a/spec/requests/api/snippets_spec.rb
+++ b/spec/requests/api/snippets_spec.rb
@@ -80,7 +80,7 @@ describe API::Snippets, api: true do
title: 'Test Title',
file_name: 'test.rb',
content: 'puts "hello world"',
- visibility_level: Gitlab::VisibilityLevel::PUBLIC
+ visibility_level: Snippet::PUBLIC
}
end
@@ -101,6 +101,36 @@ describe API::Snippets, api: true do
expect(response).to have_http_status(400)
end
+
+ context 'when the snippet is spam' do
+ def create_snippet(snippet_params = {})
+ post api('/snippets', user), params.merge(snippet_params)
+ end
+
+ before do
+ allow_any_instance_of(AkismetService).to receive(:is_spam?).and_return(true)
+ end
+
+ context 'when the snippet is private' do
+ it 'creates the snippet' do
+ expect { create_snippet(visibility_level: Snippet::PRIVATE) }.
+ to change { Snippet.count }.by(1)
+ end
+ end
+
+ context 'when the snippet is public' do
+ it 'rejects the shippet' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ not_to change { Snippet.count }
+ expect(response).to have_http_status(400)
+ end
+
+ it 'creates a spam log' do
+ expect { create_snippet(visibility_level: Snippet::PUBLIC) }.
+ to change { SpamLog.count }.by(1)
+ end
+ end
+ end
end
describe 'PUT /snippets/:id' do
diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb
index 8dbe5f0b025..1cedaa4ba63 100644
--- a/spec/requests/ci/api/builds_spec.rb
+++ b/spec/requests/ci/api/builds_spec.rb
@@ -458,7 +458,7 @@ describe Ci::API::Builds do
before { build.run! }
describe "POST /builds/:id/artifacts/authorize" do
- context "should authorize posting artifact to running build" do
+ context "authorizes posting artifact to running build" do
it "using token as parameter" do
post authorize_url, { token: build.token }, headers
@@ -492,7 +492,7 @@ describe Ci::API::Builds do
end
end
- context "should fail to post too large artifact" do
+ context "fails to post too large artifact" do
it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0)
diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb
index b7dc99ed887..f2c2009bcbf 100644
--- a/spec/services/event_create_service_spec.rb
+++ b/spec/services/event_create_service_spec.rb
@@ -9,7 +9,7 @@ describe EventCreateService, services: true do
it { expect(service.open_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.open_issue(issue, issue.author) }.to change { Event.count }
end
end
@@ -19,7 +19,7 @@ describe EventCreateService, services: true do
it { expect(service.close_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.close_issue(issue, issue.author) }.to change { Event.count }
end
end
@@ -29,7 +29,7 @@ describe EventCreateService, services: true do
it { expect(service.reopen_issue(issue, issue.author)).to be_truthy }
- it "should create new event" do
+ it "creates new event" do
expect { service.reopen_issue(issue, issue.author) }.to change { Event.count }
end
end
diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb
index 5f6a7716beb..d55a7657c0e 100644
--- a/spec/services/merge_requests/close_service_spec.rb
+++ b/spec/services/merge_requests/close_service_spec.rb
@@ -29,7 +29,7 @@ describe MergeRequests::CloseService, services: true do
it { expect(@merge_request).to be_valid }
it { expect(@merge_request).to be_closed }
- it 'should execute hooks with close action' do
+ it 'executes hooks with close action' do
expect(service).to have_received(:execute_hooks).
with(@merge_request, 'close')
end
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 10b90b40ba7..19b32c84d81 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -63,22 +63,20 @@ module CycleAnalyticsHelpers
# test case.
allow(self).to receive(:project) { other_project }
- 5.times do
- data = data_fn[self]
- start_time = Time.now
- end_time = rand(1..10).days.from_now
-
- start_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(start_time) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ start_time = Time.now
+ end_time = rand(1..10).days.from_now
- end_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(end_time) { condition_fn[self, data] }
- end
+ start_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(start_time) { condition_fn[self, data] }
+ end
- Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+ end_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(end_time) { condition_fn[self, data] }
end
+ Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
@@ -114,17 +112,15 @@ module CycleAnalyticsHelpers
context "start condition NOT PRESENT: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do
- 5.times do
- data = data_fn[self]
- end_time = rand(1..10).days.from_now
-
- end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
- Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ end_time = rand(1..10).days.from_now
- Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+ end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
+ Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
end
+ Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
+
expect(subject[phase].median).to be_nil
end
end
@@ -133,17 +129,15 @@ module CycleAnalyticsHelpers
context "start condition: #{start_time_conditions.map(&:first).to_sentence}" do
context "end condition NOT PRESENT: #{end_time_conditions.map(&:first).to_sentence}" do
it "returns nil" do
- 5.times do
- data = data_fn[self]
- start_time = Time.now
-
- start_time_conditions.each do |condition_name, condition_fn|
- Timecop.freeze(start_time) { condition_fn[self, data] }
- end
+ data = data_fn[self]
+ start_time = Time.now
- post_fn[self, data] if post_fn
+ start_time_conditions.each do |condition_name, condition_fn|
+ Timecop.freeze(start_time) { condition_fn[self, data] }
end
+ post_fn[self, data] if post_fn
+
expect(subject[phase].median).to be_nil
end
end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
index 80fc8c48fed..8d1cff7a261 100644
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -20,7 +20,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do
Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
end
- it 'should run the task without errors' do
+ it 'runs the task without errors' do
expect { run_rake_task }.not_to raise_error
end
end
diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb
index 1b910d9b91e..1f4c39eb64a 100644
--- a/spec/workers/project_destroy_worker_spec.rb
+++ b/spec/workers/project_destroy_worker_spec.rb
@@ -8,14 +8,14 @@ describe ProjectDestroyWorker do
describe "#perform" do
it "deletes the project" do
- subject.perform(project.id, project.owner, {})
+ subject.perform(project.id, project.owner.id, {})
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_falsey
end
it "deletes the project but skips repo deletion" do
- subject.perform(project.id, project.owner, { "skip_repo" => true })
+ subject.perform(project.id, project.owner.id, { "skip_repo" => true })
expect(Project.all).not_to include(project)
expect(Dir.exist?(path)).to be_truthy