Read about our upcoming Code of Conduct on this issue

Commit 6162e0ed authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 04e496dd1902
......@@ -42,6 +42,7 @@ export default {
:width="imgSize"
:class="`s${imgSize}`"
class="avatar avatar-inline m-0"
data-qa-selector="avatar_image"
/>
<i v-if="hasMergeIcon" aria-hidden="true" class="fa fa-exclamation-triangle merge-icon"></i>
</span>
......
......@@ -32,13 +32,14 @@ export default {
};
</script>
<template>
<div class="title hide-collapsed">
<div class="title hide-collapsed" data-qa-selector="assignee_title">
{{ assigneeTitle }}
<i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"></i>
<a
v-if="editable"
class="js-sidebar-dropdown-toggle edit-link float-right"
href="#"
data-qa-selector="assignee_edit_link"
data-track-event="click_edit_button"
data-track-label="right_sidebar"
data-track-property="assignee"
......
......@@ -85,7 +85,12 @@ export default {
</div>
</div>
<div v-if="renderShowMoreSection" class="user-list-more">
<button type="button" class="btn-link" @click="toggleShowLess">
<button
type="button"
class="btn-link"
data-qa-selector="more_assignees_link"
@click="toggleShowLess"
>
<template v-if="showLess">
{{ hiddenAssigneesLabel }}
</template>
......
......@@ -76,7 +76,7 @@ def link_to_member(project, author, opts = {}, &block)
link_to(author_html, user_path(author), class: "author-link js-user-link #{"#{opts[:extra_class]}" if opts[:extra_class]} #{"#{opts[:mobile_classes]}" if opts[:mobile_classes]}", data: data_attrs).html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body' }).html_safe
link_to(author_html, user_path(author), class: "author-link has-tooltip", title: title, data: { container: 'body', qa_selector: 'assignee_link' }).html_safe
end
end
......
......@@ -80,4 +80,9 @@ def spammable_text
def check_for_spam?
true
end
# Override in Spammable if differs
def allow_possible_spam?
Feature.enabled?(:allow_possible_spam, project)
end
end
......@@ -14,6 +14,7 @@ class Snippet < ApplicationRecord
include Editable
include Gitlab::SQL::Pattern
include FromUnion
extend ::Gitlab::Utils::Override
cache_markdown_field :title, pipeline: :single_line
cache_markdown_field :description
......@@ -191,6 +192,12 @@ def check_for_spam?
(public? && (title_changed? || content_changed?))
end
# snippers are the biggest sources of spam
override :allow_possible_spam?
def allow_possible_spam?
false
end
def spammable_entity_type
'snippet'
end
......
......@@ -37,7 +37,8 @@ def when_recaptcha_verified(recaptcha_verified, api = false)
else
# Otherwise, it goes to Akismet and check if it's a spam. If that's the
# case, it assigns spammable record as "spam" and create a SpamLog record.
spammable.spam = check(api)
possible_spam = check(api)
spammable.spam = possible_spam unless spammable.allow_possible_spam?
spammable.spam_log = spam_log
end
end
......
......@@ -7,4 +7,4 @@
= link_to_member(@project, assignee, name: false, title: "Assigned to :name")
- if more_assignees_count.positive?
%span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', 'original-title' => "+#{more_assignees_count} more assignees" } } +#{more_assignees_count}
%span{ class: 'avatar-counter has-tooltip', data: { container: 'body', placement: 'bottom', 'line-type' => 'old', 'original-title' => "+#{more_assignees_count} more assignees", qa_selector: 'avatar_counter' } } +#{more_assignees_count}
......@@ -313,17 +313,22 @@ Developer documentation][mdn].
## Updating the PO files with the new content
Now that the new content is marked for translation, we need to update the PO
files with the following command:
Now that the new content is marked for translation, we need to update
`locale/gitlab.pot` files with the following command:
```sh
bin/rake gettext:regenerate
```
This command will update the `locale/gitlab.pot` file with the newly externalized
This command will update `locale/gitlab.pot` file with the newly externalized
strings and remove any strings that aren't used anymore. You should check this
file in. Once the changes are on master, they will be picked up by
[Crowdin](http://translate.gitlab.com) and be presented for translation.
[Crowdin](http://translate.gitlab.com) and be presented for
translation.
We don't need to check in any changes to the
`locale/[language]/gitlab.po` files. Those will be updated in a [when
translations from Crowdin are merged](merging_translations.md).
If there are merge conflicts in the `gitlab.pot` file, you can delete the file
and regenerate it using the same command.
......
......@@ -293,7 +293,7 @@ The applications below can be uninstalled.
| ----------- | -------------- | ----- |
| Cert-Manager | 12.2+ | The associated private key will be deleted and cannot be restored. Deployed applications will continue to use HTTPS, but certificates will not be renewed. Before uninstalling, you may wish to [back up your configuration](https://docs.cert-manager.io/en/latest/tasks/backup-restore-crds.html) or [revoke your certificates](https://letsencrypt.org/docs/revoking/) |
| GitLab Runner | 12.2+ | Any running pipelines will be canceled. |
| Helm | 12.2+ | The associated Tiller pod will be deleted and cannot be restored. |
| Helm | 12.2+ | The associated Tiller pod, the `gitlab-managed-apps` namespace, and all of its resources will be deleted and cannot be restored. |
| Ingress | 12.1+ | The associated load balancer and IP will be deleted and cannot be restored. Furthermore, it can only be uninstalled if JupyterHub is not installed. |
| JupyterHub | 12.1+ | All data not committed to GitLab will be deleted and cannot be restored. |
| Knative | 12.1+ | The associated IP will be deleted and cannot be restored. |
......
......@@ -131,6 +131,10 @@ def finished_loading?
has_no_css?('.fa-spinner', wait: Capybara.default_max_wait_time)
end
def finished_loading_block?
has_no_css?('.fa-spinner.block-loading', wait: Capybara.default_max_wait_time)
end
def wait_for_animated_element(name)
# It would be ideal if we could detect when the animation is complete
# but in some cases there's nothing we can easily access via capybara
......
......@@ -5,14 +5,30 @@ module Page
module Project
module Issue
class Index < Page::Base
view 'app/helpers/projects_helper.rb' do
element :assignee_link
end
view 'app/views/projects/issues/_issue.html.haml' do
element :issue_link, 'link_to issue.title' # rubocop:disable QA/ElementWithPattern
end
view 'app/views/shared/issuable/_assignees.html.haml' do
element :avatar_counter
end
view 'app/views/shared/issuable/_nav.html.haml' do
element :closed_issues_link
end
def assignee_link_count
all_elements(:assignee_link).count
end
def avatar_counter
find_element(:avatar_counter)
end
def click_issue_link(title)
click_link(title)
end
......
......@@ -22,24 +22,54 @@ class Show < Page::Base
element :noteable_note_item
end
view 'app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue' do
element :avatar_image
end
view 'app/assets/javascripts/sidebar/components/assignees/assignee_title.vue' do
element :assignee_edit_link
element :assignee_title
end
view 'app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue' do
element :more_assignees_link
end
view 'app/helpers/dropdowns_helper.rb' do
element :dropdown_input_field
end
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
view 'app/views/shared/issuable/_close_reopen_button.html.haml' do
element :reopen_issue_button
end
view 'app/views/shared/issuable/_sidebar.html.haml' do
element :assignee_block
element :labels_block
element :edit_link_labels
element :dropdown_menu_labels
element :milestone_link
end
view 'app/views/shared/issuable/_close_reopen_button.html.haml' do
element :reopen_issue_button
view 'app/views/shared/notes/_form.html.haml' do
element :new_note_form, 'new-note' # rubocop:disable QA/ElementWithPattern
element :new_note_form, 'attr: :note' # rubocop:disable QA/ElementWithPattern
end
def assign(user)
click_element(:assignee_edit_link)
select_user(user.username)
click_body
end
def assignee_title
find_element(:assignee_title)
end
def avatar_image_count
wait_assignees_block_finish_loading do
all_elements(:avatar_image).count
end
end
def click_milestone_link
......@@ -66,6 +96,10 @@ def has_comment?(comment_text)
end
end
def more_assignees_link
find_element(:more_assignees_link)
end
def select_all_activities_filter
select_filter_with_text('Show all activity')
end
......@@ -103,6 +137,10 @@ def text_of_labels_block
find_element(:labels_block)
end
def toggle_more_assignees_link
click_element(:more_assignees_link)
end
private
def select_filter_with_text(text)
......@@ -112,6 +150,20 @@ def select_filter_with_text(text)
find_element(:filter_options, text: text).click
end
end
def select_user(username)
find("#{element_selector_css(:assignee_block)} input").set(username)
find('.dropdown-menu-user-link', text: "@#{username}").click
end
def wait_assignees_block_finish_loading
within_element(:assignee_block) do
wait(reload: false, max: 10, interval: 1) do
finished_loading_block?
yield
end
end
end
end
end
end
......
......@@ -145,6 +145,38 @@ def gitlab_qa_password_2
ENV['GITLAB_QA_PASSWORD_2']
end
def gitlab_qa_username_3
ENV['GITLAB_QA_USERNAME_3'] || 'gitlab-qa-user3'
end
def gitlab_qa_password_3
ENV['GITLAB_QA_PASSWORD_3']
end
def gitlab_qa_username_4
ENV['GITLAB_QA_USERNAME_4'] || 'gitlab-qa-user4'
end
def gitlab_qa_password_4
ENV['GITLAB_QA_PASSWORD_4']
end
def gitlab_qa_username_5
ENV['GITLAB_QA_USERNAME_5'] || 'gitlab-qa-user5'
end
def gitlab_qa_password_5
ENV['GITLAB_QA_PASSWORD_5']
end
def gitlab_qa_username_6
ENV['GITLAB_QA_USERNAME_6'] || 'gitlab-qa-user6'
end
def gitlab_qa_password_6
ENV['GITLAB_QA_PASSWORD_6']
end
def knapsack?
!!(ENV['KNAPSACK_GENERATE_REPORT'] || ENV['KNAPSACK_REPORT_PATH'] || ENV['KNAPSACK_TEST_FILE_PATTERN'])
end
......
......@@ -408,6 +408,7 @@ def reorder_issue(issue, move_after_id: nil, move_before_id: nil, group_full_pat
context 'when user has access to update issue' do
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
project.add_developer(user)
end
......@@ -421,14 +422,30 @@ def reorder_issue(issue, move_after_id: nil, move_before_id: nil, group_full_pat
context 'when Akismet is enabled and the issue is identified as spam' do
before do
stub_application_setting(recaptcha_enabled: true)
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: true)
end
end
context 'when allow_possible_spam feature flag is false' do
before do
stub_feature_flags(allow_possible_spam: false)
end
it 'renders json with recaptcha_html' do
subject
expect(json_response).to have_key('recaptcha_html')
end
end
it 'renders json with recaptcha_html' do
subject
context 'when allow_possible_spam feature flag is true' do
it 'updates the issue' do
subject
expect(json_response).to have_key('recaptcha_html')
expect(response).to have_http_status(:ok)
expect(issue.reload.title).to eq('New title')
end
end
end
end
......@@ -681,13 +698,13 @@ def go(id:)
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
stub_application_setting(recaptcha_enabled: true)
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
context 'when an issue is not identified as spam' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: false)
end
end
it 'normally updates the issue' do
......@@ -696,45 +713,64 @@ def go(id:)
end
context 'when an issue is identified as spam' do
before do
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
end
context 'when captcha is not verified' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: true)
end
end
it 'rejects an issue recognized as a spam' do
expect { update_issue }.not_to change { issue.reload.title }
end
context 'when allow_possible_spam feature flag is false' do
before do
stub_feature_flags(allow_possible_spam: false)
end
it 'rejects an issue recognized as a spam when recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
it 'rejects an issue recognized as a spam' do
expect { update_issue }.not_to change { issue.reload.title }
end
expect { update_issue }.not_to change { issue.reload.title }
end
it 'rejects an issue recognized as a spam when recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
it 'creates a spam log' do
update_issue(issue_params: { title: 'Spam title' })
expect { update_issue }.not_to change { issue.reload.title }
end
spam_logs = SpamLog.all
it 'creates a spam log' do
expect { update_issue(issue_params: { title: 'Spam title' }) }
.to log_spam(title: 'Spam title', noteable_type: 'Issue')
end
expect(spam_logs.count).to eq(1)
expect(spam_logs.first.title).to eq('Spam title')
expect(spam_logs.first.recaptcha_verified).to be_falsey
end
it 'renders recaptcha_html json response' do
update_issue
expect(json_response).to have_key('recaptcha_html')
end
it 'renders recaptcha_html json response' do
update_issue
it 'returns 200 status' do
update_issue
expect(json_response).to have_key('recaptcha_html')
expect(response).to have_gitlab_http_status(200)
end
end
it 'returns 200 status' do
update_issue
context 'when allow_possible_spam feature flag is true' do
it 'updates the issue recognized as spam' do
expect { update_issue }.to change { issue.reload.title }
end
expect(response).to have_gitlab_http_status(200)
it 'creates a spam log' do
expect { update_issue(issue_params: { title: 'Spam title' }) }
.to log_spam(
title: 'Spam title', description: issue.description,
noteable_type: 'Issue', recaptcha_verified: false
)
end
it 'returns 200 status' do
update_issue
expect(response).to have_gitlab_http_status(200)
end
end
end
......@@ -748,11 +784,6 @@ def update_verified_issue
additional_params: { spam_log_id: spam_logs.last.id, recaptcha_verification: true })
end
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha)
.and_return(true)
end
it 'returns 200 status' do
expect(response).to have_gitlab_http_status(200)
end
......@@ -917,55 +948,72 @@ def post_issue(issue_params, other_params: {})
context 'Akismet is enabled' do
before do
stub_application_setting(recaptcha_enabled: true)
allow_any_instance_of(SpamService).to receive(:check_for_spam?).and_return(true)
end
context 'when an issue is not identified as spam' do
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(false)
stub_feature_flags(allow_possible_spam: false)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: false)
end
end
it 'does not create an issue' do
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
it 'creates an issue' do
expect { post_new_issue(title: 'Some title') }.to change(Issue, :count)
end
end
context 'when an issue is identified as spam' do
before do
allow_any_instance_of(AkismetService).to receive(:spam?).and_return(true)
end
context 'when captcha is not verified' do
def post_spam_issue
post_new_issue(title: 'Spam Title', description: 'Spam lives here')
end
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(false)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(spam?: true)
end
end
it 'rejects an issue recognized as a spam' do
expect { post_spam_issue }.not_to change(Issue, :count)
end
context 'when allow_possible_spam feature flag is false' do
before do
stub_feature_flags(allow_possible_spam: false)
end
it 'creates a spam log' do
post_spam_issue
spam_logs = SpamLog.all
it 'rejects an issue recognized as a spam' do
expect { post_spam_issue }.not_to change(Issue, :count)
end
expect(spam_logs.count).to eq(1)
expect(spam_logs.first.title).to eq('Spam Title')
expect(spam_logs.first.recaptcha_verified).to be_falsey
end
it 'creates a spam log' do
expect { post_spam_issue }
.to log_spam(title: 'Spam Title', noteable_type: 'Issue', recaptcha_verified: false)
end
it 'does not create an issue when it is not valid' do
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
it 'does not create an issue when it is not valid' do
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
end
it 'does not create an issue when recaptcha is not enabled' do
stub_application_setting(recaptcha_enabled: false)
expect { post_spam_issue }.not_to change(Issue, :count)
end
end
it 'does not create an issue when recaptcha is not enabled' do
stub_application_setting(recaptcha_enabled: false)
context 'when allow_possible_spam feature flag is true' do
it 'creates an issue recognized as spam' do
expect { post_spam_issue }.to change(Issue, :count)
end
expect { post_spam_issue }.not_to change(Issue, :count)
it 'creates a spam log' do
expect { post_spam_issue }
.to log_spam(title: 'Spam Title', noteable_type: 'Issue', recaptcha_verified: false)
end
it 'does not create an issue when it is not valid' do
expect { post_new_issue(title: '') }.not_to change(Issue, :count)
end
end
end
......@@ -977,7 +1025,7 @@ def post_verified_issue
end
before do
allow_any_instance_of(described_class).to receive(:verify_recaptcha).and_return(true)
expect(controller).to receive_messages(verify_recaptcha: true)
end
it 'accepts an issue after recaptcha is verified' do
......@@ -1030,8 +1078,12 @@ def post_verified_issue
describe 'POST #mark_as_spam' do
context 'properly submits to Akismet' do
before do
allow_any_instance_of(AkismetService).to receive_messages(submit_spam: true)
allow_any_instance_of(ApplicationSetting).to receive_messages(akismet_enabled: true)
expect_next_instance_of(AkismetService) do |akismet_service|
expect(akismet_service).to receive_messages(submit_spam: true)
end
expect_next_instance_of(ApplicationSetting) do |setting|
expect(setting).to receive_messages(akismet_enabled: true)
end