diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index 68db5d8078f..55e941ed3f9 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -247,10 +247,14 @@ export default {
await this.$apollo.queries.initialCiFileContent.refetch();
},
reportFailure(type, reasons = []) {
- window.scrollTo({ top: 0, behavior: 'smooth' });
- this.showFailure = true;
- this.failureType = type;
- this.failureReasons = reasons;
+ const isCurrentFailure = this.failureType === type && this.failureReasons[0] === reasons[0];
+
+ if (!isCurrentFailure) {
+ this.showFailure = true;
+ this.failureType = type;
+ this.failureReasons = reasons;
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }
},
reportSuccess(type) {
window.scrollTo({ top: 0, behavior: 'smooth' });
diff --git a/app/assets/javascripts/snippets/components/snippet_blob_view.vue b/app/assets/javascripts/snippets/components/snippet_blob_view.vue
index 8481ac2b9c9..86cbc2c31b3 100644
--- a/app/assets/javascripts/snippets/components/snippet_blob_view.vue
+++ b/app/assets/javascripts/snippets/components/snippet_blob_view.vue
@@ -90,7 +90,7 @@ export default {
};
-
+
-
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index d2cc99302a9..f0950374182 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -590,13 +590,7 @@ export default {
:class="{ 'gl-w-full gl-order-n1 gl-mb-5': glFeatures.restructuredMrWidget }"
class="gl-display-flex gl-align-items-center gl-flex-wrap"
>
-
+
_('Code snippet') }
= render 'projects/blob/viewer', viewer: blob.simple_viewer, load_async: false, external_embed: true
diff --git a/config/feature_flags/development/find_tag_via_gitaly.yml b/config/feature_flags/development/find_tag_via_gitaly.yml
index 217eac464ad..43cbdb3993f 100644
--- a/config/feature_flags/development/find_tag_via_gitaly.yml
+++ b/config/feature_flags/development/find_tag_via_gitaly.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/340899
milestone: '14.3'
type: development
group: group::source code
-default_enabled: false
+default_enabled: true
diff --git a/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml b/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml
deleted file mode 100644
index 216726178f1..00000000000
--- a/config/feature_flags/experiment/invite_members_new_dropdown_experiment_percentage.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: invite_members_new_dropdown_experiment_percentage
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50069
-rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/291
-milestone: '13.8'
-type: experiment
-group: group::expansion
-default_enabled: false
diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb
index c2009628c56..4cc653bec43 100644
--- a/lib/gitlab/experimentation.rb
+++ b/lib/gitlab/experimentation.rb
@@ -37,9 +37,6 @@ module Gitlab
remove_known_trial_form_fields_noneditable: {
tracking_category: 'Growth::Conversion::Experiment::RemoveKnownTrialFormFieldsNoneditable',
rollout_strategy: :user
- },
- invite_members_new_dropdown: {
- tracking_category: 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
}
}.freeze
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index 7cc29cde45c..303d952381f 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -16,7 +16,7 @@ module Gitlab
included do
before_action :set_experimentation_subject_id_cookie, unless: :dnt_enabled?
- helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group, :tracking_label
+ helper_method :experiment_enabled?, :experiment_tracking_category_and_group, :record_experiment_group
end
def set_experimentation_subject_id_cookie
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6650721857d..e271417bb10 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -8338,6 +8338,9 @@ msgstr ""
msgid "Code review"
msgstr ""
+msgid "Code snippet"
+msgstr ""
+
msgid "Code snippet copied. Insert it in the correct location in the YAML file."
msgstr ""
diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
index f6afef595c6..b8755e2ced1 100644
--- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
+++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js
@@ -8,7 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import PipelineEditorTabs from '~/pipeline_editor/components/pipeline_editor_tabs.vue';
import PipelineEditorEmptyState from '~/pipeline_editor/components/ui/pipeline_editor_empty_state.vue';
import PipelineEditorMessages from '~/pipeline_editor/components/ui/pipeline_editor_messages.vue';
-import { COMMIT_SUCCESS, COMMIT_FAILURE } from '~/pipeline_editor/constants';
+import { COMMIT_SUCCESS, COMMIT_FAILURE, LOAD_FAILURE_UNKNOWN } from '~/pipeline_editor/constants';
import getBlobContent from '~/pipeline_editor/graphql/queries/blob_content.graphql';
import getCiConfigData from '~/pipeline_editor/graphql/queries/ci_config.graphql';
import getTemplate from '~/pipeline_editor/graphql/queries/get_starter_template.query.graphql';
@@ -412,6 +412,94 @@ describe('Pipeline editor app component', () => {
});
});
+ describe('when multiple errors occurs in a row', () => {
+ const updateFailureMessage = 'The GitLab CI configuration could not be updated.';
+ const unknownFailureMessage = 'The CI configuration was not loaded, please try again.';
+ const unknownReasons = ['Commit failed'];
+ const alertErrorMessage = `${updateFailureMessage} ${unknownReasons[0]}`;
+
+ const emitError = (type = COMMIT_FAILURE, reasons = unknownReasons) =>
+ findEditorHome().vm.$emit('showError', {
+ type,
+ reasons,
+ });
+
+ beforeEach(async () => {
+ mockBlobContentData.mockResolvedValue(mockBlobContentQueryResponse);
+ mockCiConfigData.mockResolvedValue(mockCiConfigQueryResponse);
+ mockLatestCommitShaQuery.mockResolvedValue(mockCommitShaResults);
+
+ window.scrollTo = jest.fn();
+
+ await createComponentWithApollo({ stubs: { PipelineEditorMessages } });
+ await emitError();
+ });
+
+ it('shows an error message for the first error', () => {
+ expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage);
+ });
+
+ it('scrolls to the top of the page to bring attention to the error message', () => {
+ expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
+ expect(window.scrollTo).toHaveBeenCalledTimes(1);
+ });
+
+ it('does not scroll to the top of the page if the same error occur multiple times in a row', async () => {
+ await emitError();
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(1);
+ expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage);
+ });
+
+ it('scrolls to the top if the error is different', async () => {
+ await emitError(LOAD_FAILURE_UNKNOWN, []);
+
+ expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage);
+ expect(window.scrollTo).toHaveBeenCalledTimes(2);
+ });
+
+ describe('when a user dismiss the alert', () => {
+ beforeEach(async () => {
+ await findAlert().vm.$emit('dismiss');
+ });
+
+ it('shows an error if the type is the same, but the reason is different', async () => {
+ const newReason = 'Something broke';
+
+ await emitError(COMMIT_FAILURE, [newReason]);
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(2);
+ expect(findAlert().text()).toMatchInterpolatedText(`${updateFailureMessage} ${newReason}`);
+ });
+
+ it('does not show an error or scroll if a new error with the same type occurs', async () => {
+ await emitError();
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(1);
+ expect(findAlert().exists()).toBe(false);
+ });
+
+ it('it shows an error and scroll when a new type is emitted', async () => {
+ await emitError(LOAD_FAILURE_UNKNOWN, []);
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(2);
+ expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage);
+ });
+
+ it('it shows an error and scroll if a previously shown type happen again', async () => {
+ await emitError(LOAD_FAILURE_UNKNOWN, []);
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(2);
+ expect(findAlert().text()).toMatchInterpolatedText(unknownFailureMessage);
+
+ await emitError();
+
+ expect(window.scrollTo).toHaveBeenCalledTimes(3);
+ expect(findAlert().text()).toMatchInterpolatedText(alertErrorMessage);
+ });
+ });
+ });
+
describe('when add_new_config_file query param is present', () => {
const originalLocation = window.location.href;
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
index f0c1da346a1..4538c1320d0 100644
--- a/spec/frontend/vue_mr_widget/mock_data.js
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -271,8 +271,6 @@ export default {
mr_troubleshooting_docs_path: 'help',
ci_troubleshooting_docs_path: 'help2',
merge_request_pipelines_docs_path: '/help/ci/pipelines/merge_request_pipelines.md',
- merge_train_when_pipeline_succeeds_docs_path:
- '/help/ci/pipelines/merge_trains.md#startadd-to-merge-train-when-pipeline-succeeds',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb
index 64f4d5ff797..ab206152e3d 100644
--- a/spec/helpers/nav/new_dropdown_helper_spec.rb
+++ b/spec/helpers/nav/new_dropdown_helper_spec.rb
@@ -13,8 +13,6 @@ RSpec.describe Nav::NewDropdownHelper do
let(:with_can_create_project) { false }
let(:with_can_create_group) { false }
let(:with_can_create_snippet) { false }
- let(:with_invite_members_experiment) { false }
- let(:with_invite_members_experiment_enabled) { false }
let(:subject) { helper.new_dropdown_view_model(project: current_project, group: current_group) }
@@ -28,11 +26,6 @@ RSpec.describe Nav::NewDropdownHelper do
end
before do
- allow(::Gitlab::Experimentation).to receive(:active?).with(:invite_members_new_dropdown) { with_invite_members_experiment }
- allow(helper).to receive(:experiment_enabled?).with(:invite_members_new_dropdown) { with_invite_members_experiment_enabled }
- allow(helper).to receive(:tracking_label) { 'test_tracking_label' }
- allow(helper).to receive(:experiment_tracking_category_and_group) { |x| x }
-
allow(helper).to receive(:current_user) { current_user }
allow(helper).to receive(:can?) { false }
@@ -42,38 +35,23 @@ RSpec.describe Nav::NewDropdownHelper do
end
shared_examples 'invite member link shared example' do
- it 'shows invite member link' do
+ it 'shows invite member link with emoji' do
expect(subject[:menu_sections]).to eq(
expected_menu_section(
title: expected_title,
menu_item: ::Gitlab::Nav::TopNavMenuItem.build(
id: 'invite',
title: 'Invite members',
+ emoji: 'shaking_hands',
href: expected_href,
data: {
- track_action: 'click_link',
- track_label: 'test_tracking_label',
- track_property: :invite_members_new_dropdown
+ track_action: 'click_link_invite_members',
+ track_label: 'plus_menu_dropdown'
}
)
)
)
end
-
- context 'with experiment enabled' do
- let(:with_invite_members_experiment_enabled) { true }
-
- it 'shows emoji with invite member link' do
- expect(subject[:menu_sections]).to match(
- expected_menu_section(
- title: expected_title,
- menu_item: a_hash_including(
- emoji: 'shaking_hands'
- )
- )
- )
- end
- end
end
it 'has title' do
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 47abfff87bb..208da345e7f 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -6,33 +6,13 @@ RSpec.describe 'layouts/header/_new_dropdown' do
let_it_be(:user) { create(:user) }
shared_examples_for 'invite member quick link' do
- context 'when an experiment is active' do
- before do
- allow(Gitlab::Experimentation).to receive(:active?).and_return(true)
- allow(view).to receive(:experiment_tracking_category_and_group)
- allow(view).to receive(:tracking_label)
- end
-
- context 'with ability to invite members' do
- it { is_expected.to have_link('Invite members', href: href) }
-
- it 'records the experiment' do
- subject
-
- expect(view).to have_received(:experiment_tracking_category_and_group)
- .with(:invite_members_new_dropdown)
- expect(view).to have_received(:tracking_label)
- end
- end
-
- context 'without ability to invite members' do
- let(:invite_member) { false }
-
- it { is_expected.not_to have_link('Invite members') }
- end
+ context 'with ability to invite members' do
+ it { is_expected.to have_link('Invite members', href: href) }
end
- context 'when experiment is not active' do
+ context 'without ability to invite members' do
+ let(:invite_member) { false }
+
it { is_expected.not_to have_link('Invite members') }
end
end
@@ -72,7 +52,6 @@ RSpec.describe 'layouts/header/_new_dropdown' do
allow(view).to receive(:can?).with(user, :create_projects, group).and_return(true)
allow(view).to receive(:can?).with(user, :admin_group_member, group).and_return(invite_member)
allow(view).to receive(:can_admin_project_member?).and_return(invite_member)
- allow(view).to receive(:experiment_enabled?)
end
subject do