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