diff --git a/app/assets/javascripts/persistent_user_callouts.js b/app/assets/javascripts/persistent_user_callouts.js index ffe235e452bd0e1db30580a34f3f16ec58c01030..120c71273fae941e40f032e8842981c6b06e0032 100644 --- a/app/assets/javascripts/persistent_user_callouts.js +++ b/app/assets/javascripts/persistent_user_callouts.js @@ -25,6 +25,7 @@ const PERSISTENT_USER_CALLOUTS = [ '.js-namespace-over-storage-users-combined-alert', '.js-joining-a-project-alert', '.js-duo-pro-trial-alert', + '.js-duo-chat-ga-alert', ]; const initCallouts = () => { diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb index 140339e1bb09f02087a56240d899666682358cc9..0781cec1ec1011e8d77428ffd5cd97bb07c75f6c 100644 --- a/app/models/users/callout.rb +++ b/app/models/users/callout.rb @@ -86,7 +86,8 @@ class Callout < MainClusterwide::ApplicationRecord transition_to_jihu_callout: 84, summarize_code_changes: 85, # EE-only duo_pro_trial_alert: 86, # EE-only - deployment_details_feedback: 87 + deployment_details_feedback: 87, + duo_chat_ga_alert: 88 # EE-only } validates :feature_name, diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 11f65b5b5a1f630de414c478e3b92fae821b93a1..96693f927ddde7bf95bbd8aad1d2a8ab6a75a8eb 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -21,6 +21,7 @@ = render_if_exists 'subscriptions/trials/alert', namespace: @group = render_if_exists 'shared/duo_pro_trial_alert', resource: @group += render_if_exists 'shared/duo_chat_ga_alert', resource: @group = render 'groups/home_panel' diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index dc550dec6669b8c1ca141a434609569f0d6f94e4..01ec7cb7a02004579312e026a60164df8af84e4a 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -6,6 +6,7 @@ - @skip_current_level_breadcrumb = true = render_if_exists 'projects/duo_pro_trial_alert', project: @project += render_if_exists 'projects/duo_chat_ga_alert', project: @project = render partial: 'flash_messages', locals: { project: @project } = render 'clusters_deprecation_alert' diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 3c3171638024d642abb9b9cd8b98aa13505804b0..4c4f5d3f2e95bbe5f11270f062062250fba83f0e 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -8,6 +8,7 @@ = render_if_exists 'shared/promotions/promote_mobile_devops', project: @project = render_if_exists 'projects/duo_pro_trial_alert', project: @project += render_if_exists 'projects/duo_chat_ga_alert', project: @project = render partial: 'flash_messages', locals: { project: @project } = render 'clusters_deprecation_alert' diff --git a/config/feature_flags/gitlab_com_derisk/duo_chat_ga_alert.yml b/config/feature_flags/gitlab_com_derisk/duo_chat_ga_alert.yml new file mode 100644 index 0000000000000000000000000000000000000000..bc0be8687cd234640ad20c5faa9f19673c04bee9 --- /dev/null +++ b/config/feature_flags/gitlab_com_derisk/duo_chat_ga_alert.yml @@ -0,0 +1,9 @@ +--- +name: duo_chat_ga_alert +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/442655 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/149329 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/455857 +milestone: '17.0' +type: gitlab_com_derisk +group: group::acquisition +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 8b880d584deffefed82fc109b1b3eb18d19a31c7..40dc9fc7b952ecafbaef16593cdd04bd87f3bdbe 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -34081,6 +34081,7 @@ Name of the feature that the callout is for. | <a id="usercalloutfeaturenameenumcluster_security_warning"></a>`CLUSTER_SECURITY_WARNING` | Callout feature name for cluster_security_warning. | | <a id="usercalloutfeaturenameenumdeployment_details_feedback"></a>`DEPLOYMENT_DETAILS_FEEDBACK` | Callout feature name for deployment_details_feedback. | | <a id="usercalloutfeaturenameenumduo_chat_callout"></a>`DUO_CHAT_CALLOUT` | Callout feature name for duo_chat_callout. | +| <a id="usercalloutfeaturenameenumduo_chat_ga_alert"></a>`DUO_CHAT_GA_ALERT` | Callout feature name for duo_chat_ga_alert. | | <a id="usercalloutfeaturenameenumduo_pro_trial_alert"></a>`DUO_PRO_TRIAL_ALERT` | Callout feature name for duo_pro_trial_alert. | | <a id="usercalloutfeaturenameenumfeature_flags_new_version"></a>`FEATURE_FLAGS_NEW_VERSION` | Callout feature name for feature_flags_new_version. | | <a id="usercalloutfeaturenameenumgcp_signup_offer"></a>`GCP_SIGNUP_OFFER` | Callout feature name for gcp_signup_offer. | diff --git a/ee/app/assets/javascripts/ai/components/global_callout/duo_chat_callout.vue b/ee/app/assets/javascripts/ai/components/global_callout/duo_chat_callout.vue index 2123905a9a8a9ca2f460c4da95257952068d4121..8808a97b479b7500a5153d9ffe37f597719eb588 100644 --- a/ee/app/assets/javascripts/ai/components/global_callout/duo_chat_callout.vue +++ b/ee/app/assets/javascripts/ai/components/global_callout/duo_chat_callout.vue @@ -5,6 +5,7 @@ import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser import DUO_CHAT_ILLUSTRATION from './callout_illustration.svg?url'; export const DUO_CHAT_GLOBAL_BUTTON_CSS_CLASS = 'js-tanuki-bot-chat-toggle'; +export const DUO_CHAT_GA_ALERT_IDENTIFIER = '.js-duo-chat-ga-alert'; const i18n = { POPOVER_LABEL: s__('DuoChat|GitLab Duo Chat'), @@ -23,6 +24,18 @@ export default { GlLink, UserCalloutDismisser, }, + computed: { + // This is a hack and needs to removed or refactored! + // This value is querying the DOM to check for another DuoChat alert + // to make sure we're not rendering 2 DuoChat alerts on top of each other. + // This needs to removed asap as soon as DUO_CHAT_GA_ALERT_IDENTIFIER is removed + // or refactored to DUO_CHAT_GA_ALERT_IDENTIFIER using global state + // without querying the DOM. + checkForGAAlert() { + return document.querySelector(DUO_CHAT_GA_ALERT_IDENTIFIER); + }, + }, + beforeMount() { const allButtons = Array.from( document.querySelectorAll(`.${DUO_CHAT_GLOBAL_BUTTON_CSS_CLASS}`), @@ -70,7 +83,7 @@ export default { <user-callout-dismisser v-if="popoverTarget" feature-name="duo_chat_callout"> <template #default="{ dismiss, shouldShowCallout }"> <gl-popover - v-if="shouldShowCallout" + v-if="shouldShowCallout && !checkForGAAlert" :target="popoverTarget" :show="shouldShowCallout" show-close-button diff --git a/ee/app/assets/javascripts/ai/tanuki_bot/index.js b/ee/app/assets/javascripts/ai/tanuki_bot/index.js index 3cd5effee45edf61f99409713b7d9187e5b5ad0f..7938c67321a4fb7d00bdb64bc632cf42b22b5648 100644 --- a/ee/app/assets/javascripts/ai/tanuki_bot/index.js +++ b/ee/app/assets/javascripts/ai/tanuki_bot/index.js @@ -18,7 +18,9 @@ export const initTanukiBotChatDrawer = () => { return false; } - const toggleEls = document.querySelectorAll('.js-tanuki-bot-chat-toggle'); + const toggleEls = document.querySelectorAll( + '.js-tanuki-bot-chat-toggle, .js-tanuki-bot-chat-ga-alert-toggle', + ); if (toggleEls.length) { toggleEls.forEach((toggleEl) => { toggleEl.addEventListener('click', () => { diff --git a/ee/app/helpers/ee/users/callouts_helper.rb b/ee/app/helpers/ee/users/callouts_helper.rb index 4f5b551aad9302b6555f14c4b76460be37680eb3..b2df5eb4bb3b1598c3a635825de73872c523e99a 100644 --- a/ee/app/helpers/ee/users/callouts_helper.rb +++ b/ee/app/helpers/ee/users/callouts_helper.rb @@ -16,6 +16,7 @@ module CalloutsHelper PROFILE_PERSONAL_ACCESS_TOKEN_EXPIRY = 'profile_personal_access_token_expiry' JOINING_A_PROJECT_ALERT = 'joining_a_project_alert' DUO_PRO_TRIAL_ALERT = 'duo_pro_trial_alert' + DUO_CHAT_GA_ALERT = 'duo_chat_ga_alert' override :render_dashboard_ultimate_trial def render_dashboard_ultimate_trial(user) @@ -83,6 +84,15 @@ def show_duo_pro_trial_alert?(group) !user_dismissed?(DUO_PRO_TRIAL_ALERT) end + def show_duo_chat_ga_alert?(group) + return false unless ::Gitlab::Saas.feature_available?(:subscriptions_trials) + return false unless ::Feature.enabled?(:duo_chat_ga_alert, group, type: :gitlab_com_derisk) + return false unless group.paid? && !group.trial? + return false unless group.member?(current_user) + + !user_dismissed?(DUO_CHAT_GA_ALERT) + end + private override :dismissed_callout? diff --git a/ee/app/views/projects/_duo_chat_ga_alert.html.haml b/ee/app/views/projects/_duo_chat_ga_alert.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..93863c1d8d4fd662bec0b046415fb4e56ea2ee4f --- /dev/null +++ b/ee/app/views/projects/_duo_chat_ga_alert.html.haml @@ -0,0 +1,3 @@ +- return if project.personal? + += render 'shared/duo_chat_ga_alert', resource: project diff --git a/ee/app/views/shared/_duo_chat_ga_alert.html.haml b/ee/app/views/shared/_duo_chat_ga_alert.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3103d5004329b67ae165633f9b503e2543a8a137 --- /dev/null +++ b/ee/app/views/shared/_duo_chat_ga_alert.html.haml @@ -0,0 +1,24 @@ +- root_namespace = resource.root_ancestor +- return unless show_duo_chat_ga_alert?(root_namespace) +- tracking_label = 'duo_chat_ga_alert' + +- content_for :page_level_alert do + %div{ class: [container_class, @content_class, 'gl-pt-5!'], data: { track_action: 'render', track_label: tracking_label } } + = render Pajamas::BannerComponent.new(close_options: { data: { testid: 'hide-duo-chat-ga-alert', track_action: 'dismiss', track_label: tracking_label }, + 'aria-label' => s_('DuoChatGAAlert|Dismiss Duo Chat GA banner') }, + banner_options: { class: 'js-duo-chat-ga-alert', + data: { feature_id: ::EE::Users::CalloutsHelper::DUO_CHAT_GA_ALERT, + dismiss_endpoint: callouts_path, + testid: 'duo-chat-ga-alert'} }) do |c| + - c.with_illustration do + = image_tag image_path('illustrations/tanuki-ai-md.svg'), alt: 'Tanuki AI Logo', class: 'gl-p-3 gl-rounded-lg bg-duo-pro rounded-bottom-right-9' + - c.with_title do + = s_('DuoChatGAAlert|GitLab Duo Chat is generally available today') + - c.with_primary_action do + = render Pajamas::ButtonComponent.new(variant: :confirm, button_options: { class: 'gl-mr-3 js-close js-tanuki-bot-chat-ga-alert-toggle', data: { track_action: 'click_button', track_label: tracking_label} }) do + = s_('DuoChatGAAlert|Use GitLab Duo Chat') + + = render Pajamas::ButtonComponent.new(href: help_page_path('user/gitlab_duo_chat', anchor: 'use-gitlab-duo-chat-in-the-web-ide'), target: "_blank") do + = s_('DuoChatGAAlert|Access Chat in the IDE') + + %p= s_('DuoChatGAAlert|Access a broad range of GitLab Duo features with your personal chat assistant in the UI of GitLab.com. You can also use Chat to access Code explanation, Code refactoring, and Test generation in your preferred IDE. Later this year, a paid add-on subscription will be required to access GitLab Duo Chat.') diff --git a/ee/spec/features/duo_chat_ga_alert_spec.rb b/ee/spec/features/duo_chat_ga_alert_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..fbe2835b8916de948bc6e9f347e420e99fd9585a --- /dev/null +++ b/ee/spec/features/duo_chat_ga_alert_spec.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Duo Chat GA alert', :saas, :js, feature_category: :duo_chat do + let_it_be(:group) { create(:group_with_plan, plan: :ultimate_plan) } + let_it_be(:user_in_group) { create(:group_member, :guest, user: create(:user), group: group).user } + + before do + stub_feature_flags(duo_chat_ga_alert: true) + sign_in(user_in_group) + end + + it 'passes axe automated accessibility testing' do + visit group_path(group) + + wait_for_requests + + expect(page).to be_axe_clean.within('[data-testid="duo-chat-ga-alert"]') # rubocop:todo Capybara/TestidFinders -- Doesn't cover use case, see https://gitlab.com/gitlab-org/gitlab/-/issues/442224 + end + + context 'with Duo Chat features enabled' do + include_context 'with ai features enabled for group' + context 'when primary CTA button clicked' do + it 'opens Duo Chat drawer' do + visit group_path(group) + + wait_for_all_requests + + click_on 'Use GitLab Duo Chat' + + expect(page).to have_selector '[data-testid="chat-component"]' + end + + it 'dismisses the alert permanently' do + visit group_path(group) + click_on 'Use GitLab Duo Chat' + + wait_for_all_requests + + expect_banner_to_be_absent + + visit group_path(group) + + expect_group_page_for(group) + expect_banner_to_be_absent + end + end + + it 'does not show the Duo Chat callout popover' do + visit group_path(group) + + wait_for_all_requests + + expect_banner_to_be_present + + expect_popover_to_be_absent + end + end + + context 'when dismiss button clicked' do + it 'is dismissed permanently' do + visit group_path(group) + dismiss_button.click + + wait_for_all_requests + + expect_banner_to_be_absent + + visit group_path(group) + + expect_group_page_for(group) + expect_banner_to_be_absent + end + end + + def dismiss_button + find('button[data-testid="hide-duo-chat-ga-alert"]') + end + + def expect_group_page_for(group) + expect(page).to have_text group.name + expect(page).to have_text "Group ID: #{group.id}" + end + + def expect_banner_to_be_present + expect(page).to have_text 'GitLab Duo Chat is generally available today' + expect(page).to have_text 'Access a broad range of GitLab Duo features with your personal chat assistant' + + expect(page).to have_link('Access Chat in the IDE', + href: help_page_path('user/gitlab_duo_chat', anchor: 'use-gitlab-duo-chat-in-the-web-ide')) + end + + def expect_banner_to_be_absent + expect(page).not_to have_text 'GitLab Duo Chat is generally available today' + expect(page).not_to have_text 'Access a broad range of GitLab Duo features with your personal chat assistant' + end + + def expect_popover_to_be_absent + expect(page).not_to have_selector '[data-testid="duo-chat-promo-callout-popover"]' + end +end diff --git a/ee/spec/frontend/ai/components/global_callout/duo_chat_callout_spec.js b/ee/spec/frontend/ai/components/global_callout/duo_chat_callout_spec.js index 530229b02c53cd84b82fdbd0ccc16f5022774b3e..1fe5fa1379c367122701f217bb347999397bd957 100644 --- a/ee/spec/frontend/ai/components/global_callout/duo_chat_callout_spec.js +++ b/ee/spec/frontend/ai/components/global_callout/duo_chat_callout_spec.js @@ -21,12 +21,15 @@ describe('DuoChatCallout', () => { const findParagraphWithinPopover = () => wrapper.find('[data-testid="duo-chat-callout-description"]'); - const createComponent = ({ shouldShowCallout = true } = {}) => { + const createComponent = ({ shouldShowCallout = true, checkForGAAlert = false } = {}) => { userCalloutDismissSpy = jest.fn(); wrapper = shallowMount(DuoChatCallout, { directives: { Outside: createMockDirective('outside'), }, + computed: { + checkForGAAlert: () => checkForGAAlert, + }, stubs: { UserCalloutDismisser: makeMockUserCalloutDismisser({ dismiss: userCalloutDismissSpy, @@ -69,6 +72,17 @@ describe('DuoChatCallout', () => { expect(findLinkWithinDismisser().exists()).toBe(false); }); + // This is a hack and needs to removed or refactored! + // This value is querying the DOM to check for another DuoChat alert + // to make sure we're not rendering 2 DuoChat alerts on top of each other. + // This needs to removed asap as soon as DUO_CHAT_GA_ALERT_IDENTIFIER is removed + // or refactored to DUO_CHAT_GA_ALERT_IDENTIFIER using global state + // without querying the DOM. + it('does not render the popover if other banner exists', () => { + createComponent({ shouldShowCallout: true, checkForGAAlert: true }); + expect(findPopoverWithinDismisser().exists()).toBe(false); + }); + it('does not throw if the popoverTarget button does not exist', () => { setHTMLFixture(`<button></button>`); expect(() => createComponent()).not.toThrow(); diff --git a/ee/spec/helpers/ee/users/callouts_helper_spec.rb b/ee/spec/helpers/ee/users/callouts_helper_spec.rb index ce467194e49627ce193b6799d588ba81c765a103..00682c218a2fc6b5ca0071a99b3cf8d671fd087f 100644 --- a/ee/spec/helpers/ee/users/callouts_helper_spec.rb +++ b/ee/spec/helpers/ee/users/callouts_helper_spec.rb @@ -321,4 +321,45 @@ it { is_expected.to eq(expected_result) } end end + + describe '.show_duo_chat_ga_alert?', :saas, feature_category: :code_suggestions do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:ultimate_plan) { create(:ultimate_plan) } + let(:group) { create(:group) } + + where( + saas_feature_available?: [true, false], + feature_flag_enabled?: [true, false], + member_of_group?: [true, false], + group_paid_and_not_trial?: [true, false], + user_dismissed_callout?: [true, false] + ) + + with_them do + before do + stub_saas_features(subscriptions_trials: saas_feature_available?) + stub_feature_flags(duo_chat_ga_alert: feature_flag_enabled?) + + allow(helper).to receive(:current_user).and_return(user) + group.add_member(user, GroupMember::GUEST) if member_of_group? + + create(:gitlab_subscription, namespace: group, hosted_plan: ultimate_plan) if group_paid_and_not_trial? + allow(helper).to receive(:user_dismissed?).with('duo_chat_ga_alert').and_return(user_dismissed_callout?) + end + + let(:expected_result) do + saas_feature_available? && + feature_flag_enabled? && + member_of_group? && + group_paid_and_not_trial? && + !user_dismissed_callout? + end + + subject { helper.show_duo_chat_ga_alert?(group) } + + it { is_expected.to eq(expected_result) } + end + end end diff --git a/ee/spec/views/groups/show.html.haml_spec.rb b/ee/spec/views/groups/show.html.haml_spec.rb index b5cc201b08610646eb8ef944211ddac7304d49d8..56b38233810b656dd71b1babdd4007546808dee3 100644 --- a/ee/spec/views/groups/show.html.haml_spec.rb +++ b/ee/spec/views/groups/show.html.haml_spec.rb @@ -14,4 +14,10 @@ expect(rendered).to render_template('shared/_duo_pro_trial_alert') end + + it 'renders the Duo Chat GA alert partial' do + render + + expect(rendered).to render_template('shared/_duo_chat_ga_alert') + end end diff --git a/ee/spec/views/projects/_duo_chat_ga_alert.html.haml_spec.rb b/ee/spec/views/projects/_duo_chat_ga_alert.html.haml_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..2773e78b6cdb6210379c4e0ffd7c174c7636b2e5 --- /dev/null +++ b/ee/spec/views/projects/_duo_chat_ga_alert.html.haml_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'projects/_duo_chat_ga_alert', :saas, feature_category: :duo_chat do + let(:user) { create(:user) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- requires for checking member + let(:group) { create(:group_with_plan, plan: :ultimate_plan) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- requires for checking member + let(:project) { nil } + + before do + group.add_developer(user) + allow(view).to receive(:project).and_return(project) + allow(view).to receive(:current_user).and_return(user) + end + + context 'with personal project' do + let(:project) { build(:project, namespace: user.namespace) } + + it 'does not render duo_chat_ga_alert template' do + # Just `render` throws an exception in the case of early return in view + # https://github.com/rails/rails/issues/41320 + view.render('projects/duo_chat_ga_alert') + + expect(rendered).not_to render_template('shared/_duo_chat_ga_alert') + end + end + + context 'with project within a group' do + let(:project) { build(:project, namespace: group) } + + it 'renders duo_chat_ga_alert template' do + render + + expect(rendered).to render_template('shared/_duo_chat_ga_alert') + end + end +end diff --git a/ee/spec/views/projects/empty.html.haml_spec.rb b/ee/spec/views/projects/empty.html.haml_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..6ee34f578fab1029e1fdb62caef685b072f726e4 --- /dev/null +++ b/ee/spec/views/projects/empty.html.haml_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'projects/empty', feature_category: :groups_and_projects do + let(:project) { ProjectPresenter.new(create(:project), current_user: build(:user)) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- sidebar rendering need to access project statistics + + before do + assign(:project, project) + end + + it 'renders the Duo Chat GA alert partial' do + render + + expect(rendered).to render_template('projects/_duo_chat_ga_alert') + end +end diff --git a/ee/spec/views/projects/show.html.haml_spec.rb b/ee/spec/views/projects/show.html.haml_spec.rb index 179b0fe8099924a31d3db25d26516ed79e1f48a7..b9f9ca2e3783bc5b4bb20e157e1ddd1e1bb22e40 100644 --- a/ee/spec/views/projects/show.html.haml_spec.rb +++ b/ee/spec/views/projects/show.html.haml_spec.rb @@ -5,16 +5,20 @@ RSpec.describe 'projects/show', feature_category: :groups_and_projects do let(:project) { ProjectPresenter.new(create(:project), current_user: build(:user)) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- sidebar rendering need to access project statistics - context 'with Duo Pro trial alert' do - before do - assign(:project, project) - allow(project).to receive(:default_view).and_return('wiki') - end + before do + assign(:project, project) + allow(project).to receive(:default_view).and_return('wiki') + end + + it 'renders the Duo Pro trial alert partial' do + render + + expect(rendered).to render_template('projects/_duo_pro_trial_alert') + end - it 'renders the Duo Pro trial alert partial' do - render + it 'renders the Duo Chat GA alert partial' do + render - expect(rendered).to render_template('projects/_duo_pro_trial_alert') - end + expect(rendered).to render_template('projects/_duo_chat_ga_alert') end end diff --git a/ee/spec/views/shared/_duo_chat_ga_alert.html.haml_spec.rb b/ee/spec/views/shared/_duo_chat_ga_alert.html.haml_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..b905cf789d9947c3e18a8c888a332b175d15b0ac --- /dev/null +++ b/ee/spec/views/shared/_duo_chat_ga_alert.html.haml_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'shared/_duo_chat_ga_alert', :saas, feature_category: :code_suggestions do + let(:user) { create(:user) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- requires for checking membership + let(:group) { create(:group_with_plan, plan: :ultimate_plan) } # rubocop:todo RSpec/FactoryBot/AvoidCreate -- requires for checking membership + + before do + group.add_developer(user) + allow(view).to receive(:resource).and_return(group) + allow(view).to receive(:current_user).and_return(user) + end + + subject(:rendered_alert) { view.content_for(:page_level_alert) } + + context 'when duo_chat_ga_alert feature flag is enabled' do + it 'renders the duo pro trial alert' do + render + + expect(rendered_alert).to have_text(s_('DuoChatGAAlert|GitLab Duo Chat is generally available today')) + expect(rendered_alert).to have_text(s_('DuoChatGAAlert|Use GitLab Duo Chat')) + expect(rendered_alert).to have_link(s_('DuoChatGAAlert|Access Chat in the IDE'), + href: help_page_path('user/gitlab_duo_chat', anchor: 'use-gitlab-duo-chat-in-the-web-ide')) + end + end + + context 'when duo_chat_ga_alert feature flag is disabled' do + before do + stub_feature_flags(duo_chat_ga_alert: false) + end + + it 'does not render the duo pro trial alert' do + # Just `render` throws an exception in the case of early return in view + # https://github.com/rails/rails/issues/41320 + view.render('shared/duo_chat_ga_alert') + + expect(rendered_alert).not_to have_text(s_('DuoChatGAAlert|GitLab Duo Chat is generally available today')) + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 14db446101c0d6d19b42736a2a9591595ef9b926..27172cc046012d8eb8783fd4137a7956a906ee8e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -19008,6 +19008,21 @@ msgstr "" msgid "Due to inactivity, this project is scheduled to be deleted on %{deletion_date}. %{link_start}Why is this scheduled?%{link_end}" msgstr "" +msgid "DuoChatGAAlert|Access Chat in the IDE" +msgstr "" + +msgid "DuoChatGAAlert|Access a broad range of GitLab Duo features with your personal chat assistant in the UI of GitLab.com. You can also use Chat to access Code explanation, Code refactoring, and Test generation in your preferred IDE. Later this year, a paid add-on subscription will be required to access GitLab Duo Chat." +msgstr "" + +msgid "DuoChatGAAlert|Dismiss Duo Chat GA banner" +msgstr "" + +msgid "DuoChatGAAlert|GitLab Duo Chat is generally available today" +msgstr "" + +msgid "DuoChatGAAlert|Use GitLab Duo Chat" +msgstr "" + msgid "DuoChat|Ask a question about GitLab" msgstr ""