Commit d1721565 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents ceb2b8de 4f2c23a7
......@@ -34,7 +34,9 @@ export default {
<gl-form-textarea
id="maintenanceBannerMessage"
v-model="bannerMessage"
:placeholder="__(`GitLab is undergoing maintenance and is operating in a read-only mode.`)"
:placeholder="
__('This GitLab instance is undergoing maintenance and is operating in read-only mode.')
"
/>
</gl-form-group>
<div class="mt-4">
......
......@@ -260,6 +260,8 @@ module SystemNotes
cross_reference = noteable_ref.to_reference(project)
body = "cloned #{direction} #{cross_reference}"
issue_activity_counter.track_issue_cloned_action(author: author) if noteable.is_a?(Issue) && direction == :to
create_note(NoteSummary.new(noteable, project, author, body, action: 'cloned'))
end
......
......@@ -37,6 +37,7 @@ module Reenqueuer
include ReenqueuerSleeper
sidekiq_options retry: false
deduplicate :none
end
def perform(*args)
......
---
title: Add usage metrics for issue clone
merge_request: 48537
author:
type: added
......@@ -11,10 +11,26 @@ module EE
override :read_only_message
def read_only_message
return _('You are on a read-only GitLab instance.') if maintenance_mode?
message = ::Gitlab::Geo.secondary? ? geo_secondary_read_only_message : super
return super unless ::Gitlab::Geo.secondary?
return message unless maintenance_mode?
return maintenance_mode_message.concat(message) if message
maintenance_mode_message
end
def maintenance_mode_message
html = tag.div do
tag.p(class: 'gl-mb-3') do
concat(sprite_icon('information-o', css_class: 'gl-icon gl-mr-3'))
concat(custom_maintenance_mode_message)
end
end
html
end
def geo_secondary_read_only_message
message = @limited_actions_message ? s_('Geo|You may be able to make a limited amount of changes or perform a limited amount of actions on this page.') : s_('Geo|If you want to make changes, you must visit the primary site.')
message = "#{message} #{lag_message}".html_safe if lag_message
......@@ -114,6 +130,11 @@ module EE
private
def custom_maintenance_mode_message
::Gitlab::CurrentSettings.maintenance_mode_message&.html_safe ||
s_('This GitLab instance is undergoing maintenance and is operating in read-only mode.')
end
def appearance
::Appearance.current
end
......
......@@ -26,6 +26,6 @@ RSpec.describe 'Geo read-only message', :geo do
stub_application_setting(maintenance_mode: true)
end
it_behaves_like 'Read-only instance', /You are on a read\-only GitLab instance./
it_behaves_like 'Read-only instance', /This GitLab instance is undergoing maintenance and is operating in read\-only mode./
end
end
......@@ -6,6 +6,8 @@ RSpec.describe ApplicationHelper do
include EE::GeoHelpers
describe '#read_only_message', :geo do
let(:default_maintenance_mode_message) { 'This GitLab instance is undergoing maintenance and is operating in read-only mode.' }
context 'when not in a Geo secondary' do
it 'returns a fallback message if database is readonly' do
expect(Gitlab::Database).to receive(:read_only?) { true }
......@@ -16,6 +18,54 @@ RSpec.describe ApplicationHelper do
it 'returns nil when database is not read_only' do
expect(helper.read_only_message).to be_nil
end
context 'maintenance mode' do
context 'enabled' do
before do
stub_application_setting(maintenance_mode: true)
end
it 'returns default message' do
expect(helper.read_only_message).to match(default_maintenance_mode_message)
end
it 'returns user set custom maintenance mode message' do
custom_message = 'Maintenance window ends at 00:00.'
stub_application_setting(maintenance_mode_message: custom_message)
expect(helper.read_only_message).to match(/#{custom_message}/)
end
context 'when database is read-only' do
it 'stacks read-only and maintenance mode messages' do
expect(Gitlab::Database).to receive(:read_only?).twice { true }
expect(helper.read_only_message).to match('You are on a read-only GitLab instance')
expect(helper.read_only_message).to match(/#{default_maintenance_mode_message}/)
end
end
end
context 'disabled' do
it 'returns nil' do
stub_application_setting(maintenance_mode: false)
expect(helper.read_only_message).to be_nil
end
end
end
end
context 'on a geo secondary' do
context 'maintenance mode on' do
it 'returns messages for both' do
expect(Gitlab::Geo).to receive(:secondary?).twice { true }
stub_application_setting(maintenance_mode: true)
expect(helper.read_only_message).to match(/you must visit the primary site/)
expect(helper.read_only_message).to match(/#{default_maintenance_mode_message}/)
end
end
end
context 'when in a Geo Secondary' do
......
......@@ -18,6 +18,7 @@ module Gitlab
ISSUE_CROSS_REFERENCED = 'g_project_management_issue_cross_referenced'
ISSUE_MOVED = 'g_project_management_issue_moved'
ISSUE_RELATED = 'g_project_management_issue_related'
ISSUE_CLONED = 'g_project_management_issue_cloned'
ISSUE_UNRELATED = 'g_project_management_issue_unrelated'
ISSUE_MARKED_AS_DUPLICATE = 'g_project_management_issue_marked_as_duplicate'
ISSUE_LOCKED = 'g_project_management_issue_locked'
......@@ -137,6 +138,10 @@ module Gitlab
track_unique_action(ISSUE_COMMENT_REMOVED, author, time)
end
def track_issue_cloned_action(author:, time: Time.zone.now)
track_unique_action(ISSUE_CLONED, author, time)
end
private
def track_unique_action(action, author, time)
......
......@@ -408,6 +408,11 @@
redis_slot: project_management
aggregation: daily
feature_flag: track_issue_activity_actions
- name: g_project_management_issue_cloned
category: issues_edit
redis_slot: project_management
aggregation: daily
feature_flag: track_issue_activity_actions
# Secrets Management
- name: i_ci_secrets_management_vault_build_created
category: ci_secrets_management
......
......@@ -12836,9 +12836,6 @@ msgstr ""
msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
msgstr ""
msgid "GitLab is undergoing maintenance and is operating in a read-only mode."
msgstr ""
msgid "GitLab member or Email address"
msgstr ""
......@@ -27804,6 +27801,9 @@ msgstr ""
msgid "This GitLab instance is licensed at the %{insufficient_license} tier. Geo is only available for users who have at least a Premium license."
msgstr ""
msgid "This GitLab instance is undergoing maintenance and is operating in read-only mode."
msgstr ""
msgid "This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring"
msgstr ""
......
tmp/
.ruby-version
.tool-versions
.ruby-gemset
urls.yml
......@@ -16,6 +16,12 @@ module QA
has_element?(:iteration_issue_link, issue_title: issue.title)
end
end
def has_no_issue?(issue)
within_element(:iteration_issues_container) do
has_no_element?(:iteration_issue_link, issue_title: issue.title)
end
end
end
end
end
......
......@@ -108,6 +108,10 @@ module QA
has_element?(:design_file_name, text: filename)
end
def has_no_design?(filename)
has_no_element?(:design_file_name, text: filename)
end
def has_created_icon?
has_element?(:design_status_icon, status: 'file-addition-solid')
end
......
......@@ -69,7 +69,7 @@ module QA
end
end
def has_no_assignee_named?(username)
def has_no_assignee?(username)
within_element(:assignee_block) do
has_no_text?(username, wait: 120)
end
......
......@@ -46,8 +46,16 @@ module QA
has_no_element?(:file_name_content, text: name)
end
def has_file_content?(text)
has_element?(:file_content, text: text)
def has_file_content?(file_content, file_number = nil)
if file_number
within_element_by_index(:file_content, file_number - 1) do
has_text?(file_content)
end
else
within_element(:file_content) do
has_text?(file_content)
end
end
end
end
end
......
......@@ -79,6 +79,10 @@ module QA
def has_issue?(issue)
has_element? :issue_container, issue_title: issue.title
end
def has_no_issue?(issue)
has_no_element? :issue_container, issue_title: issue.title
end
end
end
end
......
......@@ -49,6 +49,10 @@ module QA
has_element? :pipeline_url_link
end
def has_no_pipeline?
has_no_element? :pipeline_url_link
end
def click_run_pipeline_button
click_element :run_pipeline_button, Page::Project::Pipeline::New
end
......
......@@ -72,6 +72,10 @@ module QA
has_element? :child_pipeline
end
def has_no_child_pipeline?
has_no_element? :child_pipeline
end
def click_job(job_name)
click_element(:job_link, text: job_name)
end
......
......@@ -59,6 +59,10 @@ module QA
has_element?(:wiki_page_content, content)
end
def has_no_content?(content)
has_no_element?(:wiki_page_content, content)
end
def has_no_page?
has_element? :create_first_page_link
end
......
......@@ -40,12 +40,12 @@ module QA
issue.set_issue_assignees(assignee_ids: [user2.id])
expect(show).to have_assignee(user2.name)
expect(show).to have_no_assignee_named(user1.name)
expect(show).not_to have_assignee(user1.name)
issue.set_issue_assignees(assignee_ids: [])
expect(show).to have_no_assignee_named(user1.name)
expect(show).to have_no_assignee_named(user2.name)
expect(show).not_to have_assignee(user1.name)
expect(show).not_to have_assignee(user2.name)
end
end
end
......
......@@ -37,7 +37,7 @@ module QA
show.click_remove_related_issue_button
expect(show).to have_no_text(issue_2.title, wait: max_wait)
expect(show).not_to have_text(issue_2.title, wait: max_wait)
end
end
end
......
......@@ -16,13 +16,13 @@ module QA
Page::Dashboard::Snippet::Show.perform do |snippet|
expect(snippet).to have_snippet_title('Project snippet')
expect(snippet).to have_no_snippet_description
expect(snippet).not_to have_snippet_description
expect(snippet).to have_visibility_type(/private/i)
expect(snippet).to have_file_name('markdown_file.md')
expect(snippet).to have_file_content('Snippet heading')
expect(snippet).to have_file_content('Gitlab link')
expect(snippet).to have_no_file_content('###')
expect(snippet).to have_no_file_content('https://gitlab.com/')
expect(snippet).not_to have_file_content('###')
expect(snippet).not_to have_file_content('https://gitlab.com/')
end
end
end
......
......@@ -46,8 +46,8 @@ module QA
aggregate_failures 'file names and contents' do
expect(snippet).to have_file_name('Original file name')
expect(snippet).to have_file_content('Original file content')
expect(snippet).to have_no_file_name('Second file name')
expect(snippet).to have_no_file_content('Second file content')
expect(snippet).not_to have_file_name('Second file name')
expect(snippet).not_to have_file_content('Second file content')
end
end
end
......
......@@ -19,7 +19,7 @@ module QA
it 'user adds a CI variable', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/395' do
Page::Project::Settings::CiVariables.perform do |ci_variable|
expect(ci_variable).to have_text('VARIABLE_KEY')
expect(ci_variable).to have_no_text('some_CI_variable')
expect(ci_variable).not_to have_text('some_CI_variable')
ci_variable.click_reveal_ci_variable_value_button
......
......@@ -54,7 +54,7 @@ module QA
Page::Project::Job::Show.perform do |job|
aggregate_failures 'main CI is not overridden' do
expect(job.output).to have_no_content("#{unexpected_text}")
expect(job.output).not_to have_content("#{unexpected_text}")
expect(job.output).to have_content("#{expected_text}")
end
end
......
......@@ -112,7 +112,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
aggregate_failures 'package deletion' do
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -80,7 +80,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -118,7 +118,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -96,7 +96,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -69,7 +69,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -84,7 +84,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -104,7 +104,7 @@ module QA
Page::Project::Packages::Index.perform do |index|
aggregate_failures do
expect(index).to have_content("Package deleted successfully")
expect(index).to have_no_package(package_name)
expect(index).not_to have_package(package_name)
end
end
end
......
......@@ -88,7 +88,7 @@ module QA
Page::Project::Pipeline::Show.perform do |show|
expect(show).to have_passed
expect(show).to have_no_job("downstream_job")
expect(show).not_to have_job("downstream_job")
show.expand_child_pipeline
......
......@@ -50,8 +50,8 @@ module QA
expect(snippet).to have_file_name(file_name)
expect(snippet).to have_file_content('Geo snippet heading')
expect(snippet).to have_file_content('GitLab link')
expect(snippet).to have_no_file_content('###')
expect(snippet).to have_no_file_content('https://gitlab.com/')
expect(snippet).not_to have_file_content('###')
expect(snippet).not_to have_file_content('https://gitlab.com/')
end
end
end
......
# frozen_string_literal: true
module Matchers
module HaveAssignee
RSpec::Matchers.define :have_assignee do |assignee|
match do |page_object|
page_object.has_assignee?(assignee)
end
match_when_negated do |page_object|
page_object.has_no_assignee?(assignee)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveChildPipeline
RSpec::Matchers.define :have_child_pipeline do
match do |page_object|
page_object.has_child_pipeline?
end
match_when_negated do |page_object|
page_object.has_no_child_pipeline?
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveContent
RSpec::Matchers.define :have_content do |content|
match do |page_object|
page_object.has_content?(content)
end
match_when_negated do |page_object|
page_object.has_no_content?(content)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveDesign
RSpec::Matchers.define :have_design do |design|
match do |page_object|
page_object.has_design?(design)
end
match_when_negated do |page_object|
page_object.has_no_design?(design)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveElement
RSpec::Matchers.define :have_element do |element, **kwargs|
match do |page_object|
page_object.has_element?(element, **kwargs)
end
match_when_negated do |page_object|
page_object.has_no_element?(element, **kwargs)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveFileContent
RSpec::Matchers.define :have_file_content do |file_content, file_number|
match do |page_object|
page_object.has_file_content?(file_content, file_number)
end
match_when_negated do |page_object|
page_object.has_no_file_content?(file_content, file_number)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveIssue
RSpec::Matchers.define :have_issue do |issue|
match do |page_object|
page_object.has_issue?(issue)
end
match_when_negated do |page_object|
page_object.has_no_issue?(issue)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveJob
RSpec::Matchers.define :have_job do |job|
match do |page_object|
page_object.has_job?(job)
end
match_when_negated do |page_object|
page_object.has_no_job?(job)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HavePackage
RSpec::Matchers.define :have_package do |package|
match do |page_object|
page_object.has_package?(package)
end
match_when_negated do |page_object|
page_object.has_no_package?(package)
end
end
end
end
# frozen_string_literal: true
module Matchers
module HavePipeline
RSpec::Matchers.define :have_pipeline do
match do |page_object|
page_object.has_pipeline?
end
match_when_negated do |page_object|
page_object.has_no_pipeline?
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveRelatedIssueItem
RSpec::Matchers.define :have_related_issue_item do
match do |page_object|
page_object.has_related_issue_item?
end
match_when_negated do |page_object|
page_object.has_no_related_issue_item?
end
end
end
end
# frozen_string_literal: true
module Matchers
module HaveSnippetDescription
RSpec::Matchers.define :have_snippet_description do |description|
match do |page_object|
page_object.has_snippet_description?(description)
end
match_when_negated do |page_object|
page_object.has_no_snippet_description?
end
end
end
end
......@@ -118,6 +118,16 @@ RSpec.describe Gitlab::UsageDataCounters::IssueActivityUniqueCounter, :clean_git
end
end
context 'for Issue cloned actions' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_CLONED }
def track_action(params)
described_class.track_issue_cloned_action(**params)
end
end
end
context 'for Issue relate actions' do
it_behaves_like 'a tracked issue edit event' do
let(:action) { described_class::ISSUE_RELATED }
......
......@@ -581,6 +581,30 @@ RSpec.describe ::SystemNotes::IssuablesService do
expect { subject }.to raise_error StandardError, /Invalid direction/
end
end
context 'metrics' do
context 'cloned from' do
let(:direction) { :from }
it 'does not tracks usage' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter)
.not_to receive(:track_issue_cloned_action).with(author: author)
subject
end
end
context 'cloned to' do
let(:direction) { :to }
it 'tracks usage' do
expect(Gitlab::UsageDataCounters::IssueActivityUniqueCounter)
.to receive(:track_issue_cloned_action).with(author: author)
subject
end
end
end
end
describe '#mark_duplicate_issue' do
......
......@@ -10,6 +10,10 @@ RSpec.shared_examples 'reenqueuer' do
expect(subject.lease_timeout).to be_a(ActiveSupport::Duration)
end
it 'uses the :none deduplication strategy' do
expect(subject.class.get_deduplicate_strategy).to eq(:none)
end
describe '#perform' do
it 'tries to obtain a lease' do
expect_to_obtain_exclusive_lease(subject.lease_key)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment