diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue new file mode 100644 index 0000000000000000000000000000000000000000..693dc6a15adeb725e2da45ab6555fe368cf51b81 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue @@ -0,0 +1,15 @@ +<script> +import { s__ } from '~/locale'; + +export default { + name: 'IncludedInTrialIndicator', + i18n: { + trialOnly: s__('LearnGitlab|- Included in trial'), + }, +}; +</script> +<template> + <span class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only"> + {{ $options.i18n.trialOnly }} + </span> +</template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue index db9ef4df8afaa93be4ec5fa453d5273943da0447..54e15b6552c6e2b043de66d53cc4f71f62441210 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue @@ -38,14 +38,16 @@ export default { actionsData: this.actions, }; }, - maxValue: Object.keys(ACTION_LABELS).length, actionSections: Object.keys(ACTION_SECTIONS), computed: { + maxValue() { + return Object.keys(this.actionsData).length; + }, progressValue() { return Object.values(this.actionsData).filter((a) => a.completed).length; }, progressPercentage() { - return Math.round((this.progressValue / this.$options.maxValue) * 100); + return Math.round((this.progressValue / this.maxValue) * 100); }, }, mounted() { @@ -125,7 +127,7 @@ export default { <template #percentSymbol>%</template> </gl-sprintf> </p> - <gl-progress-bar :value="progressValue" :max="$options.maxValue" /> + <gl-progress-bar :value="progressValue" :max="maxValue" /> </div> <div class="row"> <div diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue deleted file mode 100644 index 09cc0032871c34281da54ad633a84e3a4bc55b88..0000000000000000000000000000000000000000 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue +++ /dev/null @@ -1,70 +0,0 @@ -<script> -import { GlLink, GlCard, GlIcon } from '@gitlab/ui'; -import { s__ } from '~/locale'; - -export default { - name: 'LearnGitlabInfoCard', - components: { GlLink, GlCard, GlIcon }, - i18n: { - trial: s__('Learn GitLab|Trial only'), - }, - props: { - title: { - required: true, - type: String, - }, - description: { - required: true, - type: String, - }, - actionLabel: { - required: true, - type: String, - }, - url: { - required: true, - type: String, - }, - completed: { - required: true, - type: Boolean, - }, - svg: { - required: true, - type: String, - }, - trialRequired: { - default: false, - required: false, - type: Boolean, - }, - }, -}; -</script> -<template> - <gl-card class="gl-pt-0"> - <div class="gl-text-right gl-h-5"> - <gl-icon - v-if="completed" - name="check-circle-filled" - class="gl-text-green-500" - :size="16" - data-testid="completed-icon" - /> - <span - v-else-if="trialRequired" - class="gl-text-gray-500 gl-font-sm gl-font-style-italic" - data-testid="trial-only" - >{{ $options.i18n.trial }}</span - > - </div> - <div - class="gl-text-center gl-display-flex gl-justify-content-center gl-align-items-center gl-flex-direction-column learn-gitlab-info-card-content" - > - <img :src="svg" :alt="actionLabel" /> - <h6>{{ title }}</h6> - <p class="gl-font-sm gl-text-gray-700">{{ description }}</p> - <gl-link :href="url" target="_blank" rel="noopener noreferrer" /> - </div> - </gl-card> -</template> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue index 1912477758b88a0fc5b27987969f5c420144179e..4eab0cccb0671daa3b9729704c3d2ce77632e9fd 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue @@ -6,6 +6,7 @@ import { isExperimentVariant } from '~/experimentation/utils'; import eventHub from '~/invite_members/event_hub'; import { s__, __ } from '~/locale'; import { ACTION_LABELS } from '../constants'; +import IncludedInTrialIndicator from './included_in_trial_indicator.vue'; export default { name: 'LearnGitlabSectionLink', @@ -15,12 +16,12 @@ export default { GlButton, GlPopover, GitlabExperiment, + IncludedInTrialIndicator, }, directives: { GlTooltip, }, i18n: { - trialOnly: s__('LearnGitlab|Trial only'), contactAdmin: s__('LearnGitlab|Contact your administrator to start a free Ultimate trial.'), viewAdminList: s__('LearnGitlab|View administrator list'), watchHow: __('Watch how'), @@ -41,12 +42,6 @@ export default { }; }, computed: { - linkTitle() { - return ACTION_LABELS[this.action].title; - }, - trialOnly() { - return ACTION_LABELS[this.action].trialRequired; - }, showInviteModalLink() { return ( this.action === 'userAdded' && isExperimentVariant('invite_for_help_continuous_onboarding') @@ -55,49 +50,51 @@ export default { openInNewTab() { return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true; }, - linkToVideoTutorial() { - return ACTION_LABELS[this.action].videoTutorial; - }, }, methods: { openModal() { eventHub.$emit('openModal', { source: 'learn_gitlab' }); }, + actionLabelValue(value) { + return ACTION_LABELS[this.action][value]; + }, }, }; </script> <template> <div class="gl-mb-4"> - <div v-if="trialOnly" class="gl-font-style-italic gl-text-gray-500" data-testid="trial-only"> - {{ $options.i18n.trialOnly }} - </div> <div class="flex align-items-center"> <span v-if="value.completed" class="gl-text-green-500"> <gl-icon name="check-circle-filled" :size="16" data-testid="completed-icon" /> - {{ linkTitle }} + {{ actionLabelValue('title') }} + <included-in-trial-indicator v-if="actionLabelValue('trialRequired')" /> </span> - <gl-link - v-else-if="showInviteModalLink" - data-track-action="click_link" - :data-track-label="linkTitle" - data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding" - data-testid="invite-for-help-continuous-onboarding-experiment-link" - @click="openModal" - > - {{ linkTitle }} - </gl-link> - <gl-link - v-else-if="value.enabled" - :target="openInNewTab ? '_blank' : '_self'" - :href="value.url" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - :data-track-label="linkTitle" - > - {{ linkTitle }} - </gl-link> + <div v-else-if="showInviteModalLink"> + <gl-link + data-track-action="click_link" + :data-track-label="actionLabelValue('trackLabel')" + data-track-property="Growth::Activation::Experiment::InviteForHelpContinuousOnboarding" + data-testid="invite-for-help-continuous-onboarding-experiment-link" + @click="openModal" + >{{ actionLabelValue('title') }}</gl-link + > + + <included-in-trial-indicator v-if="actionLabelValue('trialRequired')" /> + </div> + <div v-else-if="value.enabled"> + <gl-link + :target="openInNewTab ? '_blank' : '_self'" + :href="value.url" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + :data-track-label="actionLabelValue('trackLabel')" + >{{ actionLabelValue('title') }}</gl-link + > + + <included-in-trial-indicator v-if="actionLabelValue('trialRequired')" /> + </div> <template v-else> - <div data-testid="disabled-learn-gitlab-link">{{ linkTitle }}</div> + <div data-testid="disabled-learn-gitlab-link">{{ actionLabelValue('title') }}</div> <gl-button :id="popoverId" category="tertiary" @@ -127,19 +124,19 @@ export default { <template #control></template> <template #candidate> <gl-button - v-if="linkToVideoTutorial" + v-if="actionLabelValue('videoTutorial')" v-gl-tooltip category="tertiary" icon="live-preview" :title="$options.i18n.watchHow" :aria-label="$options.i18n.watchHow" - :href="linkToVideoTutorial" + :href="actionLabelValue('videoTutorial')" target="_blank" class="ml-auto" size="small" data-testid="video-tutorial-link" data-track-action="click_video_link" - :data-track-label="linkTitle" + :data-track-label="actionLabelValue('trackLabel')" data-track-property="Growth::Conversion::Experiment::LearnGitLab" data-track-experiment="video_tutorials_continuous_onboarding" /> diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js index 05bacd9b350b2f3d219b1ea364d843282af6dd7e..cb1a0302d91a6fb5c00d5cd3222e1525b2449b77 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js +++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js @@ -2,9 +2,10 @@ import { s__ } from '~/locale'; export const ACTION_LABELS = { gitWrite: { - title: s__('LearnGitLab|Create or import a repository'), - actionLabel: s__('LearnGitLab|Create or import a repository'), + title: s__('LearnGitLab|Create a repository'), + actionLabel: s__('LearnGitLab|Create a repository'), description: s__('LearnGitLab|Create or import your first repository into your new project.'), + trackLabel: 'create_a_repository', section: 'workspace', position: 1, }, @@ -14,20 +15,23 @@ export const ACTION_LABELS = { description: s__( 'LearnGitLab|GitLab works best as a team. Invite your colleague to enjoy all features.', ), + trackLabel: 'invite_your_colleagues', section: 'workspace', position: 0, }, pipelineCreated: { - title: s__('LearnGitLab|Set up CI/CD'), - actionLabel: s__('LearnGitLab|Set-up CI/CD'), + title: s__("LearnGitLab|Set up your first project's CI/CD"), + actionLabel: s__('LearnGitLab|Set up CI/CD'), description: s__('LearnGitLab|Save time by automating your integration and deployment tasks.'), + trackLabel: 'set_up_your_first_project_s_ci_cd', section: 'workspace', position: 2, }, trialStarted: { - title: s__('LearnGitLab|Start a free Ultimate trial'), + title: s__('LearnGitLab|Start a free trial of GitLab Ultimate'), actionLabel: s__('LearnGitLab|Try GitLab Ultimate for free'), description: s__('LearnGitLab|Try all GitLab features for 30 days, no credit card required.'), + trackLabel: 'start_a_free_trial_of_gitlab_ultimate', section: 'workspace', position: 3, openInNewTab: true, @@ -38,6 +42,7 @@ export const ACTION_LABELS = { description: s__( 'LearnGitLab|Prevent unexpected changes to important assets by assigning ownership of files and paths.', ), + trackLabel: 'add_code_owners', trialRequired: true, section: 'workspace', position: 4, @@ -45,9 +50,10 @@ export const ACTION_LABELS = { videoTutorial: 'https://vimeo.com/670896787', }, requiredMrApprovalsEnabled: { - title: s__('LearnGitLab|Add merge request approval'), + title: s__('LearnGitLab|Enable require merge approvals'), actionLabel: s__('LearnGitLab|Enable require merge approvals'), description: s__('LearnGitLab|Route code reviews to the right reviewers, every time.'), + trackLabel: 'enable_require_merge_approvals', trialRequired: true, section: 'workspace', position: 5, @@ -55,28 +61,52 @@ export const ACTION_LABELS = { videoTutorial: 'https://vimeo.com/670904904', }, mergeRequestCreated: { - title: s__('LearnGitLab|Submit a merge request'), + title: s__('LearnGitLab|Submit a merge request (MR)'), actionLabel: s__('LearnGitLab|Submit a merge request (MR)'), description: s__('LearnGitLab|Review and edit proposed changes to source code.'), + trackLabel: 'submit_a_merge_request_mr', section: 'plan', position: 1, }, - securityScanEnabled: { - title: s__('LearnGitLab|Run a Security scan using CI/CD'), - actionLabel: s__('LearnGitLab|Run a Security scan using CI/CD'), - description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'), - section: 'deploy', - position: 1, - }, issueCreated: { title: s__('LearnGitLab|Create an issue'), actionLabel: s__('LearnGitLab|Create an issue'), description: s__( 'LearnGitLab|Create/import issues (tickets) to collaborate on ideas and plan work.', ), + trackLabel: 'create_an_issue', section: 'plan', position: 0, }, + securityScanEnabled: { + title: s__('LearnGitLab|Run a Security scan using CI/CD'), + actionLabel: s__('LearnGitLab|Run a Security scan using CI/CD'), + description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'), + trackLabel: 'run_a_security_scan_using_ci_cd', + section: 'deploy', + position: 1, + }, + licenseScanningRun: { + title: s__('LearnGitLab|Scan dependencies for licenses'), + trackLabel: 'scan_dependencies_for_licenses', + trialRequired: true, + section: 'deploy', + position: 2, + }, + secureDependencyScanningRun: { + title: s__('LearnGitLab|Scan dependencies for vulnerabilities'), + trackLabel: 'scan_dependencies_for_vulnerabilities', + trialRequired: true, + section: 'deploy', + position: 3, + }, + secureDastRun: { + title: s__('LearnGitLab|Analyze your application for vulnerabilities with DAST'), + trackLabel: 'analyze_your_application_for_vulnerabilities_with_dast', + trialRequired: true, + section: 'deploy', + position: 4, + }, }; export const ACTION_SECTIONS = { diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue index 0e21d69310ee258977067d51c16b69be2e2e3616..ecde9235e93a7fcd2b5962dc1a379426775b8bce 100644 --- a/app/assets/javascripts/security_configuration/components/app.vue +++ b/app/assets/javascripts/security_configuration/components/app.vue @@ -206,6 +206,7 @@ export default { <template #features> <feature-card v-for="feature in augmentedSecurityFeatures" + :id="feature.anchor" :key="feature.type" data-testid="security-testing-card" :feature="feature" diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js index e4d2bd08f5054ea838b0e14d8a008c2b994e420d..6efaf08a1785d2b3f61ed24d0000dc90fde592ae 100644 --- a/app/assets/javascripts/security_configuration/components/constants.js +++ b/app/assets/javascripts/security_configuration/components/constants.js @@ -194,6 +194,7 @@ export const securityFeatures = [ helpPath: DAST_HELP_PATH, configurationHelpPath: DAST_CONFIG_HELP_PATH, type: REPORT_TYPE_DAST, + anchor: 'dast', }, { name: DEPENDENCY_SCANNING_NAME, @@ -201,6 +202,7 @@ export const securityFeatures = [ helpPath: DEPENDENCY_SCANNING_HELP_PATH, configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH, type: REPORT_TYPE_DEPENDENCY_SCANNING, + anchor: 'dependency-scanning', }, { name: CONTAINER_SCANNING_NAME, diff --git a/app/experiments/security_actions_continuous_onboarding_experiment.rb b/app/experiments/security_actions_continuous_onboarding_experiment.rb new file mode 100644 index 0000000000000000000000000000000000000000..6adfbedc744933e1167bcd3f4cb6efc291286b65 --- /dev/null +++ b/app/experiments/security_actions_continuous_onboarding_experiment.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SecurityActionsContinuousOnboardingExperiment < ApplicationExperiment + def control_behavior + end + + def candidate_behavior + end +end diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 890f7f099dfd961f8fa31efb1212747fff17c4fe..421cf84f98c021c7ef5751c0fb397196527e7f7b 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -4,6 +4,7 @@ module LearnGitlabHelper IMAGE_PATH_PLAN = "learn_gitlab/section_plan.svg" IMAGE_PATH_DEPLOY = "learn_gitlab/section_deploy.svg" IMAGE_PATH_WORKSPACE = "learn_gitlab/section_workspace.svg" + LICENSE_SCANNING_RUN_URL = 'https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html' def learn_gitlab_enabled?(project) return false unless current_user @@ -64,7 +65,7 @@ def action_urls(project) git_write: project_path(project), merge_request_created: project_merge_requests_path(project), user_added: project_members_url(project), - security_scan_enabled: project_security_configuration_path(project) + **deploy_section_action_urls(project) ) end @@ -72,6 +73,23 @@ def action_issue_urls LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } end + def deploy_section_action_urls(project) + experiment(:security_actions_continuous_onboarding, + namespace: project.namespace, + user: current_user, + sticky_to: current_user + ) do |e| + e.control { { security_scan_enabled: project_security_configuration_path(project) } } + e.candidate do + { + license_scanning_run: LICENSE_SCANNING_RUN_URL, + secure_dependency_scanning_run: project_security_configuration_path(project, anchor: 'dependency-scanning'), + secure_dast_run: project_security_configuration_path(project, anchor: 'dast') + } + end + end.run + end + def learn_gitlab_project @learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project end diff --git a/config/feature_flags/experiment/security_actions_continuous_onboarding.yml b/config/feature_flags/experiment/security_actions_continuous_onboarding.yml new file mode 100644 index 0000000000000000000000000000000000000000..ce7124cf0c8fd7710f260a7bd4df69ff6b3bbc07 --- /dev/null +++ b/config/feature_flags/experiment/security_actions_continuous_onboarding.yml @@ -0,0 +1,8 @@ +--- +name: security_actions_continuous_onboarding +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82274 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/345176 +milestone: '15.2' +type: experiment +group: group::acquisition +default_enabled: false diff --git a/ee/spec/features/uncompleted_learn_gitlab_link_spec.rb b/ee/spec/features/uncompleted_learn_gitlab_link_spec.rb index 50a1c9248f3b9469dec3c797897b4763301fca06..1ab63dea6ece001c1e2451a63ca43ea228a7fa8c 100644 --- a/ee/spec/features/uncompleted_learn_gitlab_link_spec.rb +++ b/ee/spec/features/uncompleted_learn_gitlab_link_spec.rb @@ -31,8 +31,10 @@ sign_in(user) visit namespace_project_learn_gitlab_path(namespace, project) - expect_completed_section('Set up CI/CD') - expect_completed_section('Submit a merge request') + expect_completed_section('Create an issue') + expect_completed_section('Create a repository') + expect_completed_section("Set up your first project's CI/CD") + expect_completed_section('Submit a merge request (MR)') expect_completed_section('Invite your colleagues') expect_completed_section('Run a Security scan using CI/CD') end @@ -49,10 +51,13 @@ issue_link = find_link('Create an issue') expect_correct_candidate_link(issue_link, project_issues_path(project)) - expect_correct_candidate_link(find_link('Create or import a repository'), project_path(project)) - expect_correct_candidate_link(find_link('Set up CI/CD'), project_issues_path(project, 7)) - expect_correct_candidate_link(find_link('Submit a merge request'), project_merge_requests_path(project)) + expect_correct_candidate_link(find_link('Create a repository'), project_path(project)) expect_correct_candidate_link(find_link('Invite your colleagues'), URI(project_members_url(project)).path) + expect_correct_candidate_link(find_link("Set up your first project's CI/CD"), project_issues_path(project, 7)) + expect_correct_candidate_link(find_link('Start a free trial of GitLab Ultimate'), project_issues_path(project, 2)) + expect_correct_candidate_link(find_link('Add code owners'), project_issues_path(project, 10)) + expect_correct_candidate_link(find_link('Enable require merge approvals'), project_issues_path(project, 11)) + expect_correct_candidate_link(find_link('Submit a merge request (MR)'), project_merge_requests_path(project)) expect_correct_candidate_link(find_link('Run a Security scan using CI/CD'), project_security_configuration_path(project)) issue_link.click diff --git a/lib/learn_gitlab/onboarding.rb b/lib/learn_gitlab/onboarding.rb index 42415aacbee0936666a57123b054035114c117f1..54af01a21fe65d123ea049cc6862a2e5effd3815 100644 --- a/lib/learn_gitlab/onboarding.rb +++ b/lib/learn_gitlab/onboarding.rb @@ -3,6 +3,7 @@ module LearnGitlab class Onboarding include Gitlab::Utils::StrongMemoize + include Gitlab::Experiment::Dsl ACTION_ISSUE_IDS = { pipeline_created: 7, @@ -15,12 +16,12 @@ class Onboarding :issue_created, :git_write, :merge_request_created, - :user_added, - :security_scan_enabled + :user_added ].freeze - def initialize(namespace) + def initialize(namespace, current_user = nil) @namespace = namespace + @current_user = current_user end def completed_percentage @@ -49,9 +50,20 @@ def action_columns end def tracked_actions - ACTION_ISSUE_IDS.keys + ACTION_PATHS + ACTION_ISSUE_IDS.keys + ACTION_PATHS + deploy_section_tracked_actions end - attr_reader :namespace + def deploy_section_tracked_actions + experiment(:security_actions_continuous_onboarding, + namespace: namespace, + user: current_user, + sticky_to: current_user + ) do |e| + e.control { [:security_scan_enabled] } + e.candidate { [:license_scanning_run, :secure_dependency_scanning_run, :secure_dast_run] } + end.run + end + + attr_reader :namespace, :current_user end end diff --git a/lib/sidebars/projects/menus/learn_gitlab_menu.rb b/lib/sidebars/projects/menus/learn_gitlab_menu.rb index 5de70ea7d7f2355ef197e06bf6465855e2f2c581..d2bc2fa0681643493e9d33863e554c73ae8d0c33 100644 --- a/lib/sidebars/projects/menus/learn_gitlab_menu.rb +++ b/lib/sidebars/projects/menus/learn_gitlab_menu.rb @@ -29,7 +29,10 @@ def has_pill? override :pill_count def pill_count strong_memoize(:pill_count) do - percentage = LearnGitlab::Onboarding.new(context.project.namespace).completed_percentage + percentage = LearnGitlab::Onboarding.new( + context.project.namespace, + context.current_user + ).completed_percentage "#{percentage}%" end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1e1307276fed21d3675411f337b2376f6922cc6d..0657f3bfbf6d131439cf83a9685bbbe48bf8b92c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -22753,9 +22753,6 @@ msgstr "" msgid "Learn GitLab - Ultimate trial" msgstr "" -msgid "Learn GitLab|Trial only" -msgstr "" - msgid "Learn More" msgstr "" @@ -22831,19 +22828,19 @@ msgstr "" msgid "LearnGitLab|Add code owners" msgstr "" -msgid "LearnGitLab|Add merge request approval" +msgid "LearnGitLab|Analyze your application for vulnerabilities with DAST" msgstr "" msgid "LearnGitLab|Complete these tasks first so you can enjoy GitLab's features to their fullest:" msgstr "" -msgid "LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:" +msgid "LearnGitLab|Create a repository" msgstr "" -msgid "LearnGitLab|Create an issue" +msgid "LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:" msgstr "" -msgid "LearnGitLab|Create or import a repository" +msgid "LearnGitLab|Create an issue" msgstr "" msgid "LearnGitLab|Create or import your first repository into your new project." @@ -22888,22 +22885,25 @@ msgstr "" msgid "LearnGitLab|Save time by automating your integration and deployment tasks." msgstr "" -msgid "LearnGitLab|Scan your code to uncover vulnerabilities before deploying." +msgid "LearnGitLab|Scan dependencies for licenses" msgstr "" -msgid "LearnGitLab|Set up CI/CD" +msgid "LearnGitLab|Scan dependencies for vulnerabilities" msgstr "" -msgid "LearnGitLab|Set up your workspace" +msgid "LearnGitLab|Scan your code to uncover vulnerabilities before deploying." msgstr "" -msgid "LearnGitLab|Set-up CI/CD" +msgid "LearnGitLab|Set up CI/CD" msgstr "" -msgid "LearnGitLab|Start a free Ultimate trial" +msgid "LearnGitLab|Set up your first project's CI/CD" +msgstr "" + +msgid "LearnGitLab|Set up your workspace" msgstr "" -msgid "LearnGitLab|Submit a merge request" +msgid "LearnGitLab|Start a free trial of GitLab Ultimate" msgstr "" msgid "LearnGitLab|Submit a merge request (MR)" @@ -22921,6 +22921,9 @@ msgstr "" msgid "LearnGitLab|Your team is growing! You've successfully invited new team members to the %{projectName} project." msgstr "" +msgid "LearnGitlab|- Included in trial" +msgstr "" + msgid "LearnGitlab|Contact your administrator to start a free Ultimate trial." msgstr "" @@ -22930,9 +22933,6 @@ msgstr "" msgid "LearnGitlab|Ok, let's go" msgstr "" -msgid "LearnGitlab|Trial only" -msgstr "" - msgid "LearnGitlab|View administrator list" msgstr "" diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap index 005b8968383903b3ae8b77f14b10494142a8e535..aab78c991902a2c5511e51dbee42f653a1dc0f23 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap @@ -85,8 +85,6 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > @@ -105,7 +103,8 @@ exports[`Learn GitLab renders correctly 1`] = ` </svg> Invite your colleagues - + + <!----> </span> <!----> @@ -114,8 +113,6 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > @@ -133,8 +130,9 @@ exports[`Learn GitLab renders correctly 1`] = ` /> </svg> - Create or import a repository - + Create a repository + + <!----> </span> <!----> @@ -143,23 +141,23 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Set up CI/CD" - href="http://example.com/" - target="_self" - > - - Set up CI/CD - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="set_up_your_first_project_s_ci_cd" + href="http://example.com/" + target="_self" + > + Set up your first project's CI/CD + </a> + + <!----> + </div> <!----> </div> @@ -167,24 +165,24 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Start a free Ultimate trial" - href="http://example.com/" - rel="noopener noreferrer" - target="_blank" - > - - Start a free Ultimate trial - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="start_a_free_trial_of_gitlab_ultimate" + href="http://example.com/" + rel="noopener noreferrer" + target="_blank" + > + Start a free trial of GitLab Ultimate + </a> + + <!----> + </div> <!----> </div> @@ -192,31 +190,31 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <div - class="gl-font-style-italic gl-text-gray-500" - data-testid="trial-only" - > - - Trial only - - </div> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Add code owners" - href="http://example.com/" - rel="noopener noreferrer" - target="_blank" - > - - Add code owners - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="add_code_owners" + href="http://example.com/" + rel="noopener noreferrer" + target="_blank" + > + Add code owners + </a> + + <span + class="gl-font-style-italic gl-text-gray-500" + data-testid="trial-only" + > + + - Included in trial + + </span> + </div> <!----> </div> @@ -224,31 +222,31 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <div - class="gl-font-style-italic gl-text-gray-500" - data-testid="trial-only" - > - - Trial only - - </div> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Add merge request approval" - href="http://example.com/" - rel="noopener noreferrer" - target="_blank" - > - - Add merge request approval - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="enable_require_merge_approvals" + href="http://example.com/" + rel="noopener noreferrer" + target="_blank" + > + Enable require merge approvals + </a> + + <span + class="gl-font-style-italic gl-text-gray-500" + data-testid="trial-only" + > + + - Included in trial + + </span> + </div> <!----> </div> @@ -290,23 +288,23 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Create an issue" - href="http://example.com/" - target="_self" - > - - Create an issue - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="create_an_issue" + href="http://example.com/" + target="_self" + > + Create an issue + </a> + + <!----> + </div> <!----> </div> @@ -314,23 +312,23 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Submit a merge request" - href="http://example.com/" - target="_self" - > - - Submit a merge request - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="submit_a_merge_request_mr" + href="http://example.com/" + target="_self" + > + Submit a merge request (MR) + </a> + + <!----> + </div> <!----> </div> @@ -372,24 +370,24 @@ exports[`Learn GitLab renders correctly 1`] = ` <div class="gl-mb-4" > - <!----> - <div class="flex align-items-center" > - <a - class="gl-link" - data-testid="uncompleted-learn-gitlab-link" - data-track-action="click_link" - data-track-label="Run a Security scan using CI/CD" - href="https://docs.gitlab.com/ee/foobar/" - rel="noopener noreferrer" - target="_blank" - > - - Run a Security scan using CI/CD - - </a> + <div> + <a + class="gl-link" + data-testid="uncompleted-learn-gitlab-link" + data-track-action="click_link" + data-track-label="run_a_security_scan_using_ci_cd" + href="https://docs.gitlab.com/ee/foobar/" + rel="noopener noreferrer" + target="_blank" + > + Run a Security scan using CI/CD + </a> + + <!----> + </div> <!----> </div> diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js deleted file mode 100644 index ad4bc826a9d7a99305320c9f1895a3cf7e87942a..0000000000000000000000000000000000000000 --- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import LearnGitlabInfoCard from '~/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue'; - -const defaultProps = { - title: 'Create Repository', - description: 'Some description', - actionLabel: 'Create Repository now', - url: 'https://example.com', - completed: false, - svg: 'https://example.com/illustration.svg', -}; - -describe('Learn GitLab Info Card', () => { - let wrapper; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const createWrapper = (props = {}) => { - wrapper = shallowMount(LearnGitlabInfoCard, { - propsData: { ...defaultProps, ...props }, - }); - }; - - it('renders no icon when not completed', () => { - createWrapper({ completed: false }); - - expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(false); - }); - - it('renders the completion icon when completed', () => { - createWrapper({ completed: true }); - - expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(true); - }); - - it('renders no trial only when it is not required', () => { - createWrapper(); - - expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(false); - }); - - it('renders trial only when trial is required', () => { - createWrapper({ trialRequired: true }); - - expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true); - }); - - it('renders completion icon when completed a trial-only feature', () => { - createWrapper({ trialRequired: true, completed: true }); - - expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(false); - expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(true); - }); -}); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js index d9aff37f7030842058c30b452c41b6f02271ea92..897cbf5eaa48d9899a05acec573e3419c79c6661 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js @@ -119,7 +119,7 @@ describe('Learn GitLab Section Link', () => { findUncompletedLink().trigger('click'); expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', { - label: 'Run a Security scan using CI/CD', + label: 'run_a_security_scan_using_ci_cd', }); unmockTracking(); @@ -164,7 +164,7 @@ describe('Learn GitLab Section Link', () => { triggerEvent(openInviteMembesrModalLink().element); expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', { - label: 'Invite your colleagues', + label: 'invite_your_colleagues', property: 'Growth::Activation::Experiment::InviteForHelpContinuousOnboarding', }); @@ -203,7 +203,7 @@ describe('Learn GitLab Section Link', () => { videoTutorialLink().trigger('click'); expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_video_link', { - label: 'Add code owners', + label: 'add_code_owners', property: 'Growth::Conversion::Experiment::LearnGitLab', context: { data: { diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..6ab57e31fed330e675b03f8cd1a7117463c44173 --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js @@ -0,0 +1,12 @@ +import { shallowMount } from '@vue/test-utils'; +import IncludedInTrialIndicator from '~/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue'; + +describe('Learn GitLab Trial Card', () => { + it('renders correctly', () => { + const wrapper = shallowMount(IncludedInTrialIndicator); + + expect(wrapper.text()).toEqual('- Included in trial'); + + wrapper.destroy(); + }); +}); diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb index 9fce7495b5a8eca4817a0efbfb04989946992f52..7c9dfd6b5bea436b21eb9e3f182cb667fa24c10c 100644 --- a/spec/helpers/learn_gitlab_helper_spec.rb +++ b/spec/helpers/learn_gitlab_helper_spec.rb @@ -92,38 +92,6 @@ it_behaves_like 'has all data' - it 'sets correct paths' do - expect(onboarding_actions_data).to match({ - trial_started: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/2\z}) - ), - pipeline_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/7\z}) - ), - code_owners_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/10\z}) - ), - required_mr_approvals_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/11\z}) - ), - issue_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues\z}) - ), - git_write: a_hash_including( - url: a_string_matching(%r{/learn_gitlab\z}) - ), - user_added: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/project_members\z}) - ), - merge_request_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z}) - ), - security_scan_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z}) - ) - }) - end - it 'sets correct completion statuses' do expect(onboarding_actions_data).to match({ issue_created: a_hash_including(completed: false), @@ -137,5 +105,58 @@ security_scan_enabled: a_hash_including(completed: false) }) end + + describe 'security_actions_continuous_onboarding experiment' do + let(:base_paths) do + { + trial_started: a_hash_including(url: %r{/learn_gitlab/-/issues/2\z}), + pipeline_created: a_hash_including(url: %r{/learn_gitlab/-/issues/7\z}), + code_owners_enabled: a_hash_including(url: %r{/learn_gitlab/-/issues/10\z}), + required_mr_approvals_enabled: a_hash_including(url: %r{/learn_gitlab/-/issues/11\z}), + issue_created: a_hash_including(url: %r{/learn_gitlab/-/issues\z}), + git_write: a_hash_including(url: %r{/learn_gitlab\z}), + user_added: a_hash_including(url: %r{/learn_gitlab/-/project_members\z}), + merge_request_created: a_hash_including(url: %r{/learn_gitlab/-/merge_requests\z}) + } + end + + context 'when control' do + before do + stub_experiments(security_actions_continuous_onboarding: :control) + end + + it 'sets correct paths' do + expect(onboarding_actions_data).to match( + base_paths.merge( + security_scan_enabled: a_hash_including( + url: %r{/learn_gitlab/-/security/configuration\z} + ) + ) + ) + end + end + + context 'when candidate' do + before do + stub_experiments(security_actions_continuous_onboarding: :candidate) + end + + it 'sets correct paths' do + expect(onboarding_actions_data).to match( + base_paths.merge( + license_scanning_run: a_hash_including( + url: described_class::LICENSE_SCANNING_RUN_URL + ), + secure_dependency_scanning_run: a_hash_including( + url: project_security_configuration_path(project, anchor: 'dependency-scanning') + ), + secure_dast_run: a_hash_including( + url: project_security_configuration_path(project, anchor: 'dast') + ) + ) + ) + end + end + end end end diff --git a/spec/lib/learn_gitlab/onboarding_spec.rb b/spec/lib/learn_gitlab/onboarding_spec.rb index 8c7284ed7f5053955b0b22ff0f94d726cdb2a1db..3e22ce5909130e45c3c69bb5fa5a8e8c612fcfe0 100644 --- a/spec/lib/learn_gitlab/onboarding_spec.rb +++ b/spec/lib/learn_gitlab/onboarding_spec.rb @@ -6,11 +6,14 @@ describe '#completed_percentage' do let(:completed_actions) { {} } let(:onboarding_progress) { build(:onboarding_progress, namespace: namespace, **completed_actions) } - let(:namespace) { build(:namespace) } + let(:namespace) { create(:namespace) } let_it_be(:tracked_action_columns) do - tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_PATHS - tracked_actions.map { |key| OnboardingProgress.column_name(key) } + [ + *described_class::ACTION_ISSUE_IDS.keys, + *described_class::ACTION_PATHS, + :security_scan_enabled + ].map { |key| OnboardingProgress.column_name(key) } end before do @@ -29,12 +32,6 @@ it { is_expected.to eq(0) } end - context 'when one action has been completed' do - let(:completed_actions) { Hash[tracked_action_columns.first, Time.current] } - - it { is_expected.to eq(11) } - end - context 'when all tracked actions have been completed' do let(:completed_actions) do tracked_action_columns.to_h { |action| [action, Time.current] } @@ -42,5 +39,25 @@ it { is_expected.to eq(100) } end + + describe 'security_actions_continuous_onboarding experiment' do + let(:completed_actions) { Hash[tracked_action_columns.first, Time.current] } + + context 'when control' do + before do + stub_experiments(security_actions_continuous_onboarding: :control) + end + + it { is_expected.to eq(11) } + end + + context 'when candidate' do + before do + stub_experiments(security_actions_continuous_onboarding: :candidate) + end + + it { is_expected.to eq(9) } + end + end end end