From 58b88f6b2747877de888943a2adbcbd34343ba78 Mon Sep 17 00:00:00 2001 From: Nicolas Dular <ndular@gitlab.com> Date: Mon, 1 Feb 2021 10:35:25 +0100 Subject: [PATCH] First iteration of learn gitlab page experiment This adds the route, controller and index page for the upcoming experiment where we will render two different designs of a "Learn GitLab" page and list the actions that are completed or show a link to explain how to complete the action. --- .../components/learn_gitlab_a.vue | 27 ++++++ .../components/learn_gitlab_b.vue | 27 ++++++ .../projects/learn_gitlab/constants/index.js | 12 +++ .../projects/learn_gitlab/index/index.js | 25 +++++ .../projects/learn_gitlab_controller.rb | 19 ++++ app/helpers/learn_gitlab_helper.rb | 60 ++++++++++++ app/helpers/projects_helper.rb | 2 + app/models/onboarding_progress.rb | 4 + .../layouts/nav/sidebar/_project.html.haml | 7 ++ .../projects/learn_gitlab/index.html.haml | 4 + .../learn_gitlab_a_experiment_percentage.yml | 8 ++ .../learn_gitlab_b_experiment_percentage.yml | 8 ++ config/routes/project.rb | 2 + lib/gitlab/experimentation.rb | 6 ++ locale/gitlab.pot | 24 +++++ .../projects/learn_gitlab_controller_spec.rb | 44 +++++++++ .../__snapshots__/learn_gitlab_a_spec.js.snap | 66 ++++++++++++++ .../__snapshots__/learn_gitlab_b_spec.js.snap | 66 ++++++++++++++ .../components/learn_gitlab_a_spec.js | 63 +++++++++++++ .../components/learn_gitlab_b_spec.js | 63 +++++++++++++ spec/helpers/learn_gitlab_helper_spec.rb | 91 +++++++++++++++++++ spec/helpers/projects_helper_spec.rb | 15 +++ spec/models/onboarding_progress_spec.rb | 16 ++++ 23 files changed, 659 insertions(+) create mode 100644 app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue create mode 100644 app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_b.vue create mode 100644 app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js create mode 100644 app/assets/javascripts/pages/projects/learn_gitlab/index/index.js create mode 100644 app/controllers/projects/learn_gitlab_controller.rb create mode 100644 app/helpers/learn_gitlab_helper.rb create mode 100644 app/views/projects/learn_gitlab/index.html.haml create mode 100644 config/feature_flags/experiment/learn_gitlab_a_experiment_percentage.yml create mode 100644 config/feature_flags/experiment/learn_gitlab_b_experiment_percentage.yml create mode 100644 spec/controllers/projects/learn_gitlab_controller_spec.rb create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js create mode 100644 spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js create mode 100644 spec/helpers/learn_gitlab_helper_spec.rb diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue new file mode 100644 index 00000000000..0393793bfe1 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue @@ -0,0 +1,27 @@ +<script> +import { GlLink } from '@gitlab/ui'; +import { ACTION_TEXT } from '../constants'; + +export default { + components: { GlLink }, + i18n: { + ACTION_TEXT, + }, + props: { + actions: { + required: true, + type: Object, + }, + }, +}; +</script> +<template> + <ul> + <li v-for="(value, action) in actions" :key="action"> + <span v-if="value.completed">{{ $options.i18n.ACTION_TEXT[action] }}</span> + <span v-else> + <gl-link :href="value.url">{{ $options.i18n.ACTION_TEXT[action] }}</gl-link> + </span> + </li> + </ul> +</template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_b.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_b.vue new file mode 100644 index 00000000000..0393793bfe1 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_b.vue @@ -0,0 +1,27 @@ +<script> +import { GlLink } from '@gitlab/ui'; +import { ACTION_TEXT } from '../constants'; + +export default { + components: { GlLink }, + i18n: { + ACTION_TEXT, + }, + props: { + actions: { + required: true, + type: Object, + }, + }, +}; +</script> +<template> + <ul> + <li v-for="(value, action) in actions" :key="action"> + <span v-if="value.completed">{{ $options.i18n.ACTION_TEXT[action] }}</span> + <span v-else> + <gl-link :href="value.url">{{ $options.i18n.ACTION_TEXT[action] }}</gl-link> + </span> + </li> + </ul> +</template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js new file mode 100644 index 00000000000..8606af29785 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js @@ -0,0 +1,12 @@ +import { s__ } from '~/locale'; + +export const ACTION_TEXT = { + gitWrite: s__('LearnGitLab|Create a repository'), + userAdded: s__('LearnGitLab|Invite your colleagues'), + pipelineCreated: s__('LearnGitLab|Set-up CI/CD'), + trialStarted: s__('LearnGitLab|Start a free trial of GitLab Gold'), + codeOwnersEnabled: s__('LearnGitLab|Add code owners'), + requiredMrApprovalsEnabled: s__('LearnGitLab|Enable require merge approvals'), + mergeRequestCreated: s__('LearnGitLab|Submit a merge request (MR)'), + securityScanEnabled: s__('LearnGitLab|Run a Security scan using CI/CD'), +}; diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js new file mode 100644 index 00000000000..c4dec89b984 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js @@ -0,0 +1,25 @@ +import Vue from 'vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import LearnGitlabA from '../components/learn_gitlab_a.vue'; +import LearnGitlabB from '../components/learn_gitlab_b.vue'; + +function initLearnGitlab() { + const el = document.getElementById('js-learn-gitlab-app'); + + if (!el) { + return false; + } + + const actions = convertObjectPropsToCamelCase(JSON.parse(el.dataset.actions)); + + const { learnGitlabA } = gon.experiments; + + return new Vue({ + el, + render(createElement) { + return createElement(learnGitlabA ? LearnGitlabA : LearnGitlabB, { props: { actions } }); + }, + }); +} + +initLearnGitlab(); diff --git a/app/controllers/projects/learn_gitlab_controller.rb b/app/controllers/projects/learn_gitlab_controller.rb new file mode 100644 index 00000000000..162ba9bd5cb --- /dev/null +++ b/app/controllers/projects/learn_gitlab_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class Projects::LearnGitlabController < Projects::ApplicationController + before_action :authenticate_user! + before_action :check_experiment_enabled? + + feature_category :users + + def index + push_frontend_experiment(:learn_gitlab_a, subject: current_user) + push_frontend_experiment(:learn_gitlab_b, subject: current_user) + end + + private + + def check_experiment_enabled? + return access_denied! unless helpers.learn_gitlab_experiment_enabled?(project) + end +end diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb new file mode 100644 index 00000000000..e72a9c83fc9 --- /dev/null +++ b/app/helpers/learn_gitlab_helper.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module LearnGitlabHelper + def learn_gitlab_experiment_enabled?(project) + return false unless current_user + return false unless experiment_enabled_for_user? + + learn_gitlab_onboarding_available?(project) + end + + def onboarding_actions_data(project) + attributes = onboarding_progress(project).attributes.symbolize_keys + + action_urls.map do |action, url| + [ + action, + url: url, + completed: attributes[OnboardingProgress.column_name(action)].present? + ] + end.to_h + end + + private + + ACTION_ISSUE_IDS = { + git_write: 2, + pipeline_created: 4, + merge_request_created: 6, + user_added: 7, + trial_started: 13, + required_mr_approvals_enabled: 15, + code_owners_enabled: 16 + }.freeze + + ACTION_DOC_URLS = { + security_scan_enabled: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports' + }.freeze + + def action_urls + ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) }.merge(ACTION_DOC_URLS) + end + + def learn_gitlab_project + @learn_gitlab_project ||= LearnGitlab.new(current_user).project + end + + def onboarding_progress(project) + OnboardingProgress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord + end + + def experiment_enabled_for_user? + Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_a, subject: current_user) || + Gitlab::Experimentation.in_experiment_group?(:learn_gitlab_b, subject: current_user) + end + + def learn_gitlab_onboarding_available?(project) + OnboardingProgress.onboarding?(project.namespace) && + LearnGitlab.new(current_user).available? + end +end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a2e9952f350..f5cd89d96b4 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -433,6 +433,8 @@ module ProjectsHelper nav_tabs += package_nav_tabs(project, current_user) + nav_tabs << :learn_gitlab if learn_gitlab_experiment_enabled?(project) + nav_tabs end # rubocop:enable Metrics/CyclomaticComplexity diff --git a/app/models/onboarding_progress.rb b/app/models/onboarding_progress.rb index 38a9489a3ad..8a444f8934e 100644 --- a/app/models/onboarding_progress.rb +++ b/app/models/onboarding_progress.rb @@ -47,6 +47,10 @@ class OnboardingProgress < ApplicationRecord safe_find_or_create_by(namespace: namespace) end + def onboarding?(namespace) + where(namespace: namespace).any? + end + def register(namespace, action) return unless root_namespace?(namespace) && ACTIONS.include?(action) diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml index 822fe3fea88..c8e9546ac85 100644 --- a/app/views/layouts/nav/sidebar/_project.html.haml +++ b/app/views/layouts/nav/sidebar/_project.html.haml @@ -33,6 +33,13 @@ = link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do %span= _('Releases') + - if project_nav_tab? :learn_gitlab + = nav_link(controller: :learn_gitlab, html_options: { class: 'home' }) do + = link_to project_learn_gitlab_path(@project) do + .nav-icon-container + = sprite_icon('home') + %span.nav-item-name + = _('Learn GitLab') - if project_nav_tab? :files = nav_link(controller: sidebar_repository_paths, unless: -> { current_path?('projects/graphs#charts') }) do diff --git a/app/views/projects/learn_gitlab/index.html.haml b/app/views/projects/learn_gitlab/index.html.haml new file mode 100644 index 00000000000..d5fdbc10eb4 --- /dev/null +++ b/app/views/projects/learn_gitlab/index.html.haml @@ -0,0 +1,4 @@ +- breadcrumb_title _("Learn GitLab") +- page_title _("Learn GitLab") + +#js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json } } diff --git a/config/feature_flags/experiment/learn_gitlab_a_experiment_percentage.yml b/config/feature_flags/experiment/learn_gitlab_a_experiment_percentage.yml new file mode 100644 index 00000000000..54b7ea465f1 --- /dev/null +++ b/config/feature_flags/experiment/learn_gitlab_a_experiment_percentage.yml @@ -0,0 +1,8 @@ +--- +name: learn_gitlab_a_experiment_percentage +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53089 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/281022 +milestone: '13.9' +type: experiment +group: group::conversion +default_enabled: false diff --git a/config/feature_flags/experiment/learn_gitlab_b_experiment_percentage.yml b/config/feature_flags/experiment/learn_gitlab_b_experiment_percentage.yml new file mode 100644 index 00000000000..cca5d35baf3 --- /dev/null +++ b/config/feature_flags/experiment/learn_gitlab_b_experiment_percentage.yml @@ -0,0 +1,8 @@ +--- +name: learn_gitlab_b_experiment_percentage +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53089 +rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/306 +milestone: '13.9' +type: experiment +group: group::conversion +default_enabled: false diff --git a/config/routes/project.rb b/config/routes/project.rb index e6df2532479..21dfe173715 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -87,6 +87,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do end end + get :learn_gitlab, action: :index, controller: 'learn_gitlab' + namespace :ci do resource :lint, only: [:show, :create] resource :pipeline_editor, only: [:show], controller: :pipeline_editor, path: 'editor' diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb index 0deda3596f5..423f238a0a2 100644 --- a/lib/gitlab/experimentation.rb +++ b/lib/gitlab/experimentation.rb @@ -95,6 +95,12 @@ module Gitlab trial_onboarding_issues: { tracking_category: 'Growth::Conversion::Experiment::TrialOnboardingIssues' }, + learn_gitlab_a: { + tracking_category: 'Growth::Conversion::Experiment::LearnGitLabA' + }, + learn_gitlab_b: { + tracking_category: 'Growth::Activation::Experiment::LearnGitLabB' + }, in_product_marketing_emails: { tracking_category: 'Growth::Activation::Experiment::InProductMarketingEmails' } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 3b948e7fdda..8528dbb3c36 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17386,6 +17386,30 @@ msgstr "" msgid "Learn more." msgstr "" +msgid "LearnGitLab|Add code owners" +msgstr "" + +msgid "LearnGitLab|Create a repository" +msgstr "" + +msgid "LearnGitLab|Enable require merge approvals" +msgstr "" + +msgid "LearnGitLab|Invite your colleagues" +msgstr "" + +msgid "LearnGitLab|Run a Security scan using CI/CD" +msgstr "" + +msgid "LearnGitLab|Set-up CI/CD" +msgstr "" + +msgid "LearnGitLab|Start a free trial of GitLab Gold" +msgstr "" + +msgid "LearnGitLab|Submit a merge request (MR)" +msgstr "" + msgid "Leave" msgstr "" diff --git a/spec/controllers/projects/learn_gitlab_controller_spec.rb b/spec/controllers/projects/learn_gitlab_controller_spec.rb new file mode 100644 index 00000000000..f633f7aa246 --- /dev/null +++ b/spec/controllers/projects/learn_gitlab_controller_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Projects::LearnGitlabController do + describe 'GET #index' do + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } + + let(:learn_gitlab_experiment_enabled) { true } + let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } + + subject { get :index, params: params } + + before do + allow(controller.helpers).to receive(:learn_gitlab_experiment_enabled?).and_return(learn_gitlab_experiment_enabled) + end + + context 'unauthenticated user' do + it { is_expected.to have_gitlab_http_status(:redirect) } + end + + context 'authenticated user' do + before do + sign_in(user) + end + + it { is_expected.to render_template(:index) } + + it 'pushes experiment to frontend' do + expect(controller).to receive(:push_frontend_experiment).with(:learn_gitlab_a, subject: user) + expect(controller).to receive(:push_frontend_experiment).with(:learn_gitlab_b, subject: user) + + subject + end + + context 'learn_gitlab experiment not enabled' do + let(:learn_gitlab_experiment_enabled) { false } + + it { is_expected.to have_gitlab_http_status(:not_found) } + end + end + end +end diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap new file mode 100644 index 00000000000..c9141d13a46 --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Learn GitLab Design A should render the loading state 1`] = ` +<ul> + <li> + <span> + Create a repository + </span> + </li> + <li> + <span> + Invite your colleagues + </span> + </li> + <li> + <span> + Set-up CI/CD + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Start a free trial of GitLab Gold + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Add code owners + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Enable require merge approvals + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Submit a merge request (MR) + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Run a Security scan using CI/CD + </gl-link-stub> + </span> + </li> +</ul> +`; diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap new file mode 100644 index 00000000000..85e3b675e5b --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Learn GitLab Design B should render the loading state 1`] = ` +<ul> + <li> + <span> + Create a repository + </span> + </li> + <li> + <span> + Invite your colleagues + </span> + </li> + <li> + <span> + Set-up CI/CD + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Start a free trial of GitLab Gold + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Add code owners + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Enable require merge approvals + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Submit a merge request (MR) + </gl-link-stub> + </span> + </li> + <li> + <span> + <gl-link-stub + href="http://example.com/" + > + Run a Security scan using CI/CD + </gl-link-stub> + </span> + </li> +</ul> +`; diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js new file mode 100644 index 00000000000..ddc5339e7e0 --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js @@ -0,0 +1,63 @@ +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue'; + +const TEST_ACTIONS = { + gitWrite: { + url: 'http://example.com/', + completed: true, + }, + userAdded: { + url: 'http://example.com/', + completed: true, + }, + pipelineCreated: { + url: 'http://example.com/', + completed: true, + }, + trialStarted: { + url: 'http://example.com/', + completed: false, + }, + codeOwnersEnabled: { + url: 'http://example.com/', + completed: false, + }, + requiredMrApprovalsEnabled: { + url: 'http://example.com/', + completed: false, + }, + mergeRequestCreated: { + url: 'http://example.com/', + completed: false, + }, + securityScanEnabled: { + url: 'http://example.com/', + completed: false, + }, +}; + +describe('Learn GitLab Design A', () => { + let wrapper; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const createWrapper = () => { + wrapper = extendedWrapper( + shallowMount(LearnGitlabA, { + propsData: { + actions: TEST_ACTIONS, + }, + }), + ); + }; + + it('should render the loading state', () => { + createWrapper(); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js new file mode 100644 index 00000000000..be4f5768402 --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js @@ -0,0 +1,63 @@ +import { shallowMount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue'; + +const TEST_ACTIONS = { + gitWrite: { + url: 'http://example.com/', + completed: true, + }, + userAdded: { + url: 'http://example.com/', + completed: true, + }, + pipelineCreated: { + url: 'http://example.com/', + completed: true, + }, + trialStarted: { + url: 'http://example.com/', + completed: false, + }, + codeOwnersEnabled: { + url: 'http://example.com/', + completed: false, + }, + requiredMrApprovalsEnabled: { + url: 'http://example.com/', + completed: false, + }, + mergeRequestCreated: { + url: 'http://example.com/', + completed: false, + }, + securityScanEnabled: { + url: 'http://example.com/', + completed: false, + }, +}; + +describe('Learn GitLab Design B', () => { + let wrapper; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const createWrapper = () => { + wrapper = extendedWrapper( + shallowMount(LearnGitlabA, { + propsData: { + actions: TEST_ACTIONS, + }, + }), + ); + }; + + it('should render the loading state', () => { + createWrapper(); + + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb new file mode 100644 index 00000000000..f789eb9d940 --- /dev/null +++ b/spec/helpers/learn_gitlab_helper_spec.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe LearnGitlabHelper do + include AfterNextHelpers + include Devise::Test::ControllerHelpers + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, name: LearnGitlab::PROJECT_NAME, namespace: user.namespace) } + let_it_be(:namespace) { project.namespace } + + before do + project.add_developer(user) + + allow(helper).to receive(:user).and_return(user) + allow_next_instance_of(LearnGitlab) do |learn_gitlab| + allow(learn_gitlab).to receive(:project).and_return(project) + end + + OnboardingProgress.onboard(namespace) + OnboardingProgress.register(namespace, :git_write) + end + + describe '.onboarding_actions_data' do + subject(:onboarding_actions_data) { helper.onboarding_actions_data(project) } + + it 'has all actions' do + expect(onboarding_actions_data.keys).to contain_exactly( + :git_write, + :pipeline_created, + :merge_request_created, + :user_added, + :trial_started, + :required_mr_approvals_enabled, + :code_owners_enabled, + :security_scan_enabled + ) + end + + it 'sets correct path and completion status' do + expect(onboarding_actions_data[:git_write]).to eq({ + url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:git_write]), + completed: true + }) + expect(onboarding_actions_data[:pipeline_created]).to eq({ + url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:pipeline_created]), + completed: false + }) + end + end + + describe '.learn_gitlab_experiment_enabled?' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:project) { create(:project, namespace: user.namespace) } + + let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } + + subject { helper.learn_gitlab_experiment_enabled?(project) } + + where(:experiment_a, :experiment_b, :onboarding, :learn_gitlab_available, :result) do + true | false | true | true | true + false | true | true | true | true + false | false | true | true | false + true | true | true | false | false + true | true | false | true | false + end + + with_them do + before do + stub_experiment_for_subject(learn_gitlab_a: experiment_a, learn_gitlab_b: experiment_b) + allow(OnboardingProgress).to receive(:onboarding?).with(project.namespace).and_return(onboarding) + allow_next(LearnGitlab, user).to receive(:available?).and_return(learn_gitlab_available) + end + + context 'when signed in' do + before do + sign_in(user) + end + + it { is_expected.to eq(result) } + end + + context 'when not signed in' do + it { is_expected.to eq(false) } + end + end + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index b61db537159..303e3c78153 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' RSpec.describe ProjectsHelper do include ProjectForksHelper + include AfterNextHelpers let_it_be_with_reload(:project) { create(:project) } let_it_be_with_refind(:project_with_repo) { create(:project, :repository) } @@ -498,6 +499,20 @@ RSpec.describe ProjectsHelper do it { is_expected.not_to include(:confluence) } it { is_expected.to include(:wiki) } end + + context 'learn gitlab experiment' do + context 'when it is enabled' do + before do + expect(helper).to receive(:learn_gitlab_experiment_enabled?).with(project).and_return(true) + end + + it { is_expected.to include(:learn_gitlab) } + end + + context 'when it is not enabled' do + it { is_expected.not_to include(:learn_gitlab) } + end + end end describe '#can_view_operations_tab?' do diff --git a/spec/models/onboarding_progress_spec.rb b/spec/models/onboarding_progress_spec.rb index 02fe0015a14..0aa19345a25 100644 --- a/spec/models/onboarding_progress_spec.rb +++ b/spec/models/onboarding_progress_spec.rb @@ -114,6 +114,22 @@ RSpec.describe OnboardingProgress do end end + describe '.onboarding?' do + subject(:onboarding?) { described_class.onboarding?(namespace) } + + context 'when onboarded' do + before do + described_class.onboard(namespace) + end + + it { is_expected.to eq true } + end + + context 'when not onboarding' do + it { is_expected.to eq false } + end + end + describe '.register' do subject(:register_action) { described_class.register(namespace, action) } -- 2.30.9