From 87cf0605b5109f62847d7cca96e92f43fadca7d5 Mon Sep 17 00:00:00 2001
From: Doug Stull <dstull@gitlab.com>
Date: Mon, 30 Jan 2023 14:24:15 +0000
Subject: [PATCH] Move learn gitlab code to ee

- only ever used in ee and never used in foss
---
 .rubocop_todo/gitlab/doc_url.yml              |   2 +-
 .rubocop_todo/gitlab/strong_memoize_attr.yml  |   1 -
 .../layout/first_hash_element_indentation.yml |   1 -
 .rubocop_todo/layout/line_length.yml          |   1 -
 .rubocop_todo/rspec/context_wording.yml       |   1 -
 .rubocop_todo/rspec/expect_in_hook.yml        |   1 -
 .../rspec/factory_bot/avoid_create.yml        |   3 +-
 .../rspec/missing_feature_category.yml        |   4 -
 .rubocop_todo/style/if_unless_modifier.yml    |   1 -
 ...orials_continuous_onboarding_experiment.rb |   6 -
 app/helpers/invite_members_helper.rb          |  10 +-
 app/helpers/learn_gitlab_helper.rb            | 106 -------
 app/helpers/sidebars_helper.rb                |   1 -
 config/routes/project.rb                      |   2 -
 .../included_in_trial_indicator.vue           |   0
 .../learn_gitlab/components/learn_gitlab.vue  |   0
 .../components/learn_gitlab_section_card.vue  |   0
 .../components/learn_gitlab_section_link.vue  |   0
 .../projects/learn_gitlab/constants/index.js  |   0
 .../projects/learn_gitlab/index/index.js      |   0
 .../projects/learn_gitlab_controller.rb       |   2 +-
 ee/app/helpers/ee/invite_members_helper.rb    |  10 +
 ee/app/helpers/ee/learn_gitlab_helper.rb      |  59 ----
 ee/app/helpers/ee/sidebars_helper.rb          |   3 +-
 .../helpers/projects/learn_gitlab_helper.rb   | 131 +++++++++
 .../app}/models/onboarding/learn_gitlab.rb    |  11 +-
 .../standard_namespace_create_service.rb      |   4 +-
 .../projects/learn_gitlab/index.html.haml     |   3 +-
 ...nuous_onboarding_getting_started.html.haml |   5 +-
 .../invite_for_help_continuous_onboarding.yml |   0
 .../video_tutorials_continuous_onboarding.yml |   0
 ee/config/routes/project.rb                   |   2 +
 ee/lib/ee/sidebars/projects/panel.rb          |  10 +-
 .../projects/menus/learn_gitlab_menu.rb       |  14 +-
 .../projects/learn_gitlab_controller_spec.rb  |  17 +-
 .../learn_gitlab_section_card_spec.js.snap    |   0
 .../__snapshots__/learn_gitlab_spec.js.snap   |   0
 .../learn_gitlab_section_card_spec.js         |   2 +-
 .../learn_gitlab_section_link_spec.js         |   4 +-
 .../components/learn_gitlab_spec.js           |   4 +-
 .../learn_gitlab_trial_card_spec.js           |   2 +-
 .../learn_gitlab/components/mock_data.js      |   0
 .../helpers/ee/invite_members_helper_spec.rb  |  60 ++++
 .../helpers/ee/learn_gitlab_helper_spec.rb    | 144 ----------
 .../projects/learn_gitlab_helper_spec.rb      | 266 ++++++++++++++++++
 .../lib/ee/sidebars/projects/panel_spec.rb    |   6 +
 .../projects/menus/learn_gitlab_menu_spec.rb  |  17 +-
 .../models/onboarding/learn_gitlab_spec.rb    |  72 +++--
 .../standard_namespace_create_service_spec.rb |   4 +-
 .../nav/sidebar/_project.html.haml_spec.rb    |  18 +-
 ...boarding_getting_started.html.haml_spec.rb |   2 +-
 .../create_learn_gitlab_worker_spec.rb        |  19 +-
 lib/sidebars/projects/panel.rb                |   1 -
 ...s_continuous_onboarding_experiment_spec.rb |   9 -
 spec/helpers/invite_members_helper_spec.rb    |  52 +---
 spec/helpers/learn_gitlab_helper_spec.rb      | 162 -----------
 spec/support/rspec_order_todo.yml             |   4 -
 .../nav/sidebar/_project.html.haml_spec.rb    |  15 +-
 58 files changed, 629 insertions(+), 645 deletions(-)
 delete mode 100644 app/experiments/video_tutorials_continuous_onboarding_experiment.rb
 delete mode 100644 app/helpers/learn_gitlab_helper.rb
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue (100%)
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue (100%)
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue (100%)
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue (100%)
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/constants/index.js (100%)
 rename {app => ee/app}/assets/javascripts/pages/projects/learn_gitlab/index/index.js (100%)
 rename {app => ee/app}/controllers/projects/learn_gitlab_controller.rb (89%)
 delete mode 100644 ee/app/helpers/ee/learn_gitlab_helper.rb
 create mode 100644 ee/app/helpers/projects/learn_gitlab_helper.rb
 rename {app => ee/app}/models/onboarding/learn_gitlab.rb (72%)
 rename {app => ee/app}/views/projects/learn_gitlab/index.html.haml (71%)
 rename {config => ee/config}/feature_flags/experiment/invite_for_help_continuous_onboarding.yml (100%)
 rename {config => ee/config}/feature_flags/experiment/video_tutorials_continuous_onboarding.yml (100%)
 rename {lib => ee/lib}/sidebars/projects/menus/learn_gitlab_menu.rb (81%)
 rename {spec => ee/spec}/controllers/projects/learn_gitlab_controller_spec.rb (70%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap (100%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap (100%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js (84%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js (97%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js (94%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js (71%)
 rename {spec => ee/spec}/frontend/pages/projects/learn_gitlab/components/mock_data.js (100%)
 delete mode 100644 ee/spec/helpers/ee/learn_gitlab_helper_spec.rb
 create mode 100644 ee/spec/helpers/projects/learn_gitlab_helper_spec.rb
 rename {spec => ee/spec}/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb (77%)
 rename {spec => ee/spec}/models/onboarding/learn_gitlab_spec.rb (54%)
 delete mode 100644 spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb
 delete mode 100644 spec/helpers/learn_gitlab_helper_spec.rb

diff --git a/.rubocop_todo/gitlab/doc_url.yml b/.rubocop_todo/gitlab/doc_url.yml
index 9c781ca64e91..9935af055552 100644
--- a/.rubocop_todo/gitlab/doc_url.yml
+++ b/.rubocop_todo/gitlab/doc_url.yml
@@ -7,7 +7,7 @@ Gitlab/DocUrl:
     - 'app/graphql/types/merge_request_type.rb'
     - 'app/graphql/types/notes/diff_position_input_type.rb'
     - 'app/graphql/types/query_complexity_type.rb'
-    - 'app/helpers/learn_gitlab_helper.rb'
+    - 'ee/app/helpers/projects/learn_gitlab_helper.rb'
     - 'app/models/integrations/apple_app_store.rb'
     - 'app/models/integrations/microsoft_teams.rb'
     - 'app/presenters/dev_ops_report/metric_presenter.rb'
diff --git a/.rubocop_todo/gitlab/strong_memoize_attr.yml b/.rubocop_todo/gitlab/strong_memoize_attr.yml
index 066f17aabe0d..e0a243aec8e8 100644
--- a/.rubocop_todo/gitlab/strong_memoize_attr.yml
+++ b/.rubocop_todo/gitlab/strong_memoize_attr.yml
@@ -726,6 +726,5 @@ Gitlab/StrongMemoizeAttr:
     - 'lib/sidebars/groups/menus/merge_requests_menu.rb'
     - 'lib/sidebars/projects/menus/analytics_menu.rb'
     - 'lib/sidebars/projects/menus/issues_menu.rb'
-    - 'lib/sidebars/projects/menus/learn_gitlab_menu.rb'
     - 'lib/unnested_in_filters/rewriter.rb'
     - 'tooling/graphql/docs/helper.rb'
diff --git a/.rubocop_todo/layout/first_hash_element_indentation.yml b/.rubocop_todo/layout/first_hash_element_indentation.yml
index facc7a58f1a2..ef0e5fbe040d 100644
--- a/.rubocop_todo/layout/first_hash_element_indentation.yml
+++ b/.rubocop_todo/layout/first_hash_element_indentation.yml
@@ -194,7 +194,6 @@ Layout/FirstHashElementIndentation:
     - 'spec/frontend/fixtures/autocomplete_sources.rb'
     - 'spec/graphql/types/ci/detailed_status_type_spec.rb'
     - 'spec/helpers/groups/observability_helper_spec.rb'
-    - 'spec/helpers/learn_gitlab_helper_spec.rb'
     - 'spec/helpers/projects/pages_helper_spec.rb'
     - 'spec/helpers/routing/pseudonymization_helper_spec.rb'
     - 'spec/initializers/rack_multipart_patch_spec.rb'
diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml
index 5e3b05e064d9..b8ef2820dadb 100644
--- a/.rubocop_todo/layout/line_length.yml
+++ b/.rubocop_todo/layout/line_length.yml
@@ -4041,7 +4041,6 @@ Layout/LineLength:
     - 'spec/helpers/groups/group_members_helper_spec.rb'
     - 'spec/helpers/groups_helper_spec.rb'
     - 'spec/helpers/icons_helper_spec.rb'
-    - 'spec/helpers/invite_members_helper_spec.rb'
     - 'spec/helpers/issuables_helper_spec.rb'
     - 'spec/helpers/issues_helper_spec.rb'
     - 'spec/helpers/labels_helper_spec.rb'
diff --git a/.rubocop_todo/rspec/context_wording.yml b/.rubocop_todo/rspec/context_wording.yml
index b55e290f95ad..e710e590a152 100644
--- a/.rubocop_todo/rspec/context_wording.yml
+++ b/.rubocop_todo/rspec/context_wording.yml
@@ -1525,7 +1525,6 @@ RSpec/ContextWording:
     - 'spec/helpers/groups_helper_spec.rb'
     - 'spec/helpers/ide_helper_spec.rb'
     - 'spec/helpers/integrations_helper_spec.rb'
-    - 'spec/helpers/invite_members_helper_spec.rb'
     - 'spec/helpers/jira_connect_helper_spec.rb'
     - 'spec/helpers/labels_helper_spec.rb'
     - 'spec/helpers/listbox_helper_spec.rb'
diff --git a/.rubocop_todo/rspec/expect_in_hook.yml b/.rubocop_todo/rspec/expect_in_hook.yml
index d25c4137c1a2..ae886e6e85e7 100644
--- a/.rubocop_todo/rspec/expect_in_hook.yml
+++ b/.rubocop_todo/rspec/expect_in_hook.yml
@@ -163,7 +163,6 @@ RSpec/ExpectInHook:
     - 'spec/graphql/mutations/design_management/move_spec.rb'
     - 'spec/helpers/commits_helper_spec.rb'
     - 'spec/helpers/groups_helper_spec.rb'
-    - 'spec/helpers/invite_members_helper_spec.rb'
     - 'spec/helpers/projects_helper_spec.rb'
     - 'spec/helpers/search_helper_spec.rb'
     - 'spec/helpers/users_helper_spec.rb'
diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
index 8fb2327f049b..a857841f17ad 100644
--- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml
+++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml
@@ -36,7 +36,7 @@ RSpec/FactoryBot/AvoidCreate:
     - 'ee/spec/helpers/ee/issuables_helper_spec.rb'
     - 'ee/spec/helpers/ee/issues_helper_spec.rb'
     - 'ee/spec/helpers/ee/labels_helper_spec.rb'
-    - 'ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
+    - 'ee/spec/helpers/projects/learn_gitlab_helper_spec.rb'
     - 'ee/spec/helpers/ee/lock_helper_spec.rb'
     - 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
     - 'ee/spec/helpers/ee/namespaces_helper_spec.rb'
@@ -305,7 +305,6 @@ RSpec/FactoryBot/AvoidCreate:
     - 'spec/helpers/keyset_helper_spec.rb'
     - 'spec/helpers/labels_helper_spec.rb'
     - 'spec/helpers/lazy_image_tag_helper_spec.rb'
-    - 'spec/helpers/learn_gitlab_helper_spec.rb'
     - 'spec/helpers/markup_helper_spec.rb'
     - 'spec/helpers/members_helper_spec.rb'
     - 'spec/helpers/merge_requests_helper_spec.rb'
diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml
index 018e98d123de..8a9367ec2e7d 100644
--- a/.rubocop_todo/rspec/missing_feature_category.yml
+++ b/.rubocop_todo/rspec/missing_feature_category.yml
@@ -584,7 +584,6 @@ RSpec/MissingFeatureCategory:
     - 'ee/spec/helpers/ee/invite_members_helper_spec.rb'
     - 'ee/spec/helpers/ee/issues_helper_spec.rb'
     - 'ee/spec/helpers/ee/labels_helper_spec.rb'
-    - 'ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
     - 'ee/spec/helpers/ee/lock_helper_spec.rb'
     - 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
     - 'ee/spec/helpers/ee/namespaces_helper_spec.rb'
@@ -2583,7 +2582,6 @@ RSpec/MissingFeatureCategory:
     - 'ee/spec/workers/merge_trains/refresh_worker_spec.rb'
     - 'ee/spec/workers/namespaces/sync_namespace_name_worker_spec.rb'
     - 'ee/spec/workers/new_epic_worker_spec.rb'
-    - 'ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb'
     - 'ee/spec/workers/personal_access_tokens/groups/policy_worker_spec.rb'
     - 'ee/spec/workers/personal_access_tokens/instance/policy_worker_spec.rb'
     - 'ee/spec/workers/post_receive_spec.rb'
@@ -6051,7 +6049,6 @@ RSpec/MissingFeatureCategory:
     - 'spec/lib/sidebars/projects/menus/infrastructure_menu_spec.rb'
     - 'spec/lib/sidebars/projects/menus/invite_team_members_menu_spec.rb'
     - 'spec/lib/sidebars/projects/menus/issues_menu_spec.rb'
-    - 'spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb'
     - 'spec/lib/sidebars/projects/menus/merge_requests_menu_spec.rb'
     - 'spec/lib/sidebars/projects/menus/monitor_menu_spec.rb'
     - 'spec/lib/sidebars/projects/menus/packages_registries_menu_spec.rb'
@@ -6555,7 +6552,6 @@ RSpec/MissingFeatureCategory:
     - 'spec/models/oauth_access_grant_spec.rb'
     - 'spec/models/oauth_access_token_spec.rb'
     - 'spec/models/onboarding/completion_spec.rb'
-    - 'spec/models/onboarding/learn_gitlab_spec.rb'
     - 'spec/models/onboarding/progress_spec.rb'
     - 'spec/models/operations/feature_flag_spec.rb'
     - 'spec/models/operations/feature_flags/strategy_spec.rb'
diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml
index 39f4094d40f3..3794232db319 100644
--- a/.rubocop_todo/style/if_unless_modifier.yml
+++ b/.rubocop_todo/style/if_unless_modifier.yml
@@ -1065,7 +1065,6 @@ Style/IfUnlessModifier:
     - 'spec/features/projects/tree/create_file_spec.rb'
     - 'spec/graphql/mutations/releases/update_spec.rb'
     - 'spec/helpers/application_settings_helper_spec.rb'
-    - 'spec/helpers/invite_members_helper_spec.rb'
     - 'spec/lib/container_registry/gitlab_api_client_spec.rb'
     - 'spec/lib/gitlab/background_migration/disable_expiration_policies_linked_to_no_container_images_spec.rb'
     - 'spec/lib/gitlab/config/entry/validators/nested_array_helpers_spec.rb'
diff --git a/app/experiments/video_tutorials_continuous_onboarding_experiment.rb b/app/experiments/video_tutorials_continuous_onboarding_experiment.rb
deleted file mode 100644
index 2c5790f83d13..000000000000
--- a/app/experiments/video_tutorials_continuous_onboarding_experiment.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# frozen_string_literal: true
-
-class VideoTutorialsContinuousOnboardingExperiment < ApplicationExperiment
-  control {}
-  candidate {}
-end
diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb
index 6fad13464263..e986b56fde49 100644
--- a/app/helpers/invite_members_helper.rb
+++ b/app/helpers/invite_members_helper.rb
@@ -45,7 +45,7 @@ def common_invite_modal_dataset(source)
       full_path: source.full_path
     }
 
-    if show_invite_members_for_task?(source)
+    if current_user && show_invite_members_for_task?(source)
       dataset.merge!(
         tasks_to_be_done_options: tasks_to_be_done_options.to_json,
         projects: projects_for_source(source).to_json,
@@ -71,11 +71,9 @@ def users_filter_data(group)
     {}
   end
 
-  def show_invite_members_for_task?(source)
-    return unless current_user
-
-    invite_for_help_continuous_onboarding = source.is_a?(Project) && experiment(:invite_for_help_continuous_onboarding, namespace: source.namespace).assigned.name == 'candidate'
-    params[:open_modal] == 'invite_members_for_task' || invite_for_help_continuous_onboarding
+  # Overridden in EE
+  def show_invite_members_for_task?(_source)
+    params[:open_modal] == 'invite_members_for_task'
   end
 
   def tasks_to_be_done_options
diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb
deleted file mode 100644
index e1241f8b5724..000000000000
--- a/app/helpers/learn_gitlab_helper.rb
+++ /dev/null
@@ -1,106 +0,0 @@
-# frozen_string_literal: true
-
-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
-
-    learn_gitlab_onboarding_available?(project)
-  end
-
-  def learn_gitlab_data(project)
-    {
-      actions: onboarding_actions_data(project).to_json,
-      sections: onboarding_sections_data.to_json,
-      project: onboarding_project_data(project).to_json
-    }
-  end
-
-  def learn_gitlab_onboarding_available?(project)
-    Onboarding::Progress.onboarding?(project.namespace) &&
-      Onboarding::LearnGitlab.new(current_user).available?
-  end
-
-  private
-
-  def onboarding_actions_data(project)
-    attributes = onboarding_progress(project).attributes.symbolize_keys
-
-    action_urls(project).to_h do |action, url|
-      [
-        action,
-        {
-          url: url,
-          completed: attributes[Onboarding::Progress.column_name(action)].present?,
-          enabled: true
-        }
-      ]
-    end
-  end
-
-  def onboarding_sections_data
-    {
-      workspace: {
-        svg: image_path(IMAGE_PATH_WORKSPACE)
-      },
-      plan: {
-        svg: image_path(IMAGE_PATH_PLAN)
-      },
-      deploy: {
-        svg: image_path(IMAGE_PATH_DEPLOY)
-      }
-    }
-  end
-
-  def onboarding_project_data(project)
-    { name: project.name }
-  end
-
-  def action_urls(project)
-    action_issue_urls.merge(
-      issue_created: project_issues_path(project),
-      git_write: project_path(project),
-      merge_request_created: project_merge_requests_path(project),
-      user_added: project_members_url(project),
-      **deploy_section_action_urls(project)
-    )
-  end
-
-  def action_issue_urls
-    Onboarding::Completion::ACTION_ISSUE_IDS.transform_values do |id|
-      project_issue_url(learn_gitlab_project, id)
-    end
-  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 ||= Onboarding::LearnGitlab.new(current_user).project
-  end
-
-  def onboarding_progress(project)
-    Onboarding::Progress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
-  end
-end
-
-LearnGitlabHelper.prepend_mod_with('LearnGitlabHelper')
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 9ce68df26a18..5a19ce6149a1 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -117,7 +117,6 @@ def project_sidebar_context_data(project, user, current_ref, ref_type: nil)
     {
       current_user: user,
       container: project,
-      learn_gitlab_enabled: learn_gitlab_enabled?(project),
       current_ref: current_ref,
       ref_type: ref_type,
       jira_issues_integration: project_jira_issues_integration?,
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 4a2f83752495..ceb0671c0343 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -92,8 +92,6 @@
           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/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue b/ee/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue b/ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab.vue
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue b/ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/ee/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js
diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js b/ee/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
similarity index 100%
rename from app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
rename to ee/app/assets/javascripts/pages/projects/learn_gitlab/index/index.js
diff --git a/app/controllers/projects/learn_gitlab_controller.rb b/ee/app/controllers/projects/learn_gitlab_controller.rb
similarity index 89%
rename from app/controllers/projects/learn_gitlab_controller.rb
rename to ee/app/controllers/projects/learn_gitlab_controller.rb
index 0193fcc7be62..acf728a8d12e 100644
--- a/app/controllers/projects/learn_gitlab_controller.rb
+++ b/ee/app/controllers/projects/learn_gitlab_controller.rb
@@ -15,7 +15,7 @@ def index; end
     private
 
     def verify_learn_gitlab_available!
-      access_denied! unless helpers.learn_gitlab_enabled?(project)
+      access_denied! unless Onboarding::LearnGitlab.new(current_user).onboarding_and_available?(project.namespace)
     end
 
     def enable_invite_for_help_continuous_onboarding_experiment
diff --git a/ee/app/helpers/ee/invite_members_helper.rb b/ee/app/helpers/ee/invite_members_helper.rb
index b23651ef0f12..fba88a77e4e8 100644
--- a/ee/app/helpers/ee/invite_members_helper.rb
+++ b/ee/app/helpers/ee/invite_members_helper.rb
@@ -56,5 +56,15 @@ def users_filter_data(group)
 
       { users_filter: 'saml_provider_id', filter_id: root_group.saml_provider.id }
     end
+
+    private
+
+    override :show_invite_members_for_task?
+    def show_invite_members_for_task?(source)
+      invite_for_help_continuous_onboarding = source.is_a?(Project) &&
+        experiment(:invite_for_help_continuous_onboarding, namespace: source.namespace).assigned.name == 'candidate'
+
+      super || invite_for_help_continuous_onboarding
+    end
   end
 end
diff --git a/ee/app/helpers/ee/learn_gitlab_helper.rb b/ee/app/helpers/ee/learn_gitlab_helper.rb
deleted file mode 100644
index e2fd41e41f09..000000000000
--- a/ee/app/helpers/ee/learn_gitlab_helper.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-module EE
-  module LearnGitlabHelper
-    extend ::Gitlab::Utils::Override
-
-    GITLAB_COM = 'gitlab.com'
-    ONBOARDING_START_TRIAL = 'onboarding-start-trial'
-    ONBOARDING_REQUIRE_MR_APPROVALS = 'onboarding-require-merge-approvals'
-    ONBOARDING_CODE_OWNERS = 'onboarding-code-owners'
-
-    private
-
-    override :action_urls
-    def action_urls(project)
-      urls = super(project)
-
-      return urls unless ::Gitlab::CurrentSettings.should_check_namespace_plan?
-
-      trial_items = {
-        trial_started: project_project_members_path(project),
-        required_mr_approvals_enabled: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
-        code_owners_enabled: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners')
-      }
-
-      if can_start_trial?(project)
-        trial_items = {
-          trial_started: new_trial_path_with_glm(content: ONBOARDING_START_TRIAL),
-          required_mr_approvals_enabled: new_trial_path_with_glm(content: ONBOARDING_REQUIRE_MR_APPROVALS),
-          code_owners_enabled: new_trial_path_with_glm(content: ONBOARDING_CODE_OWNERS)
-        }
-      end
-
-      urls.merge(trial_items)
-    end
-
-    override :onboarding_actions_data
-    def onboarding_actions_data(project)
-      action_urls = super(project)
-
-      if ::Gitlab::CurrentSettings.should_check_namespace_plan? && !can_start_trial?(project)
-        action_urls[:trial_started][:enabled] = false
-        action_urls[:trial_started][:message] =
-          s_('LearnGitlab|Contact your administrator to start a free Ultimate trial.')
-      end
-
-      action_urls
-    end
-
-    def can_start_trial?(project)
-      root = project&.root_ancestor
-      root&.has_free_or_no_subscription? && can?(current_user, :admin_namespace, root)
-    end
-
-    def new_trial_path_with_glm(content:, source: GITLAB_COM)
-      new_trial_path({ glm_source: source, glm_content: content })
-    end
-  end
-end
diff --git a/ee/app/helpers/ee/sidebars_helper.rb b/ee/app/helpers/ee/sidebars_helper.rb
index 825588949d3a..db5c645fa077 100644
--- a/ee/app/helpers/ee/sidebars_helper.rb
+++ b/ee/app/helpers/ee/sidebars_helper.rb
@@ -8,7 +8,8 @@ module SidebarsHelper
     def project_sidebar_context_data(project, user, current_ref, **args)
       super.merge({
         show_promotions: show_promotions?(user),
-        show_discover_project_security: show_discover_project_security?(project)
+        show_discover_project_security: show_discover_project_security?(project),
+        learn_gitlab_enabled: Onboarding::LearnGitlab.new(user).onboarding_and_available?(project.namespace)
       })
     end
 
diff --git a/ee/app/helpers/projects/learn_gitlab_helper.rb b/ee/app/helpers/projects/learn_gitlab_helper.rb
new file mode 100644
index 000000000000..13b684d80f1b
--- /dev/null
+++ b/ee/app/helpers/projects/learn_gitlab_helper.rb
@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+module Projects
+  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'
+    GITLAB_COM = 'gitlab.com'
+    ONBOARDING_START_TRIAL = 'onboarding-start-trial'
+    ONBOARDING_REQUIRE_MR_APPROVALS = 'onboarding-require-merge-approvals'
+    ONBOARDING_CODE_OWNERS = 'onboarding-code-owners'
+
+    def learn_gitlab_data(project)
+      {
+        actions: onboarding_actions_data(project).to_json,
+        sections: onboarding_sections_data.to_json,
+        project: onboarding_project_data(project).to_json
+      }
+    end
+
+    def learn_gitlab_onboarding_available?(namespace)
+      Onboarding::LearnGitlab.new(current_user).onboarding_and_available?(namespace)
+    end
+
+    private
+
+    def onboarding_actions_data(project)
+      onboarding_progress = Onboarding::Progress.find_by(namespace: project.namespace) # rubocop: disable CodeReuse/ActiveRecord
+      attributes = onboarding_progress.attributes.symbolize_keys
+
+      data = action_urls(project).to_h do |action, url|
+        [
+          action,
+          {
+            url: url,
+            completed: attributes[Onboarding::Progress.column_name(action)].present?,
+            enabled: true
+          }
+        ]
+      end
+
+      if ::Gitlab::CurrentSettings.should_check_namespace_plan? && !can_start_trial?(project)
+        data[:trial_started][:enabled] = false
+        data[:trial_started][:message] =
+          s_('LearnGitlab|Contact your administrator to start a free Ultimate trial.')
+      end
+
+      data
+    end
+
+    def can_start_trial?(project)
+      root = project.root_ancestor
+      root.has_free_or_no_subscription? && can?(current_user, :admin_namespace, root)
+    end
+
+    def onboarding_sections_data
+      {
+        workspace: {
+          svg: image_path(IMAGE_PATH_WORKSPACE)
+        },
+        plan: {
+          svg: image_path(IMAGE_PATH_PLAN)
+        },
+        deploy: {
+          svg: image_path(IMAGE_PATH_DEPLOY)
+        }
+      }
+    end
+
+    def onboarding_project_data(project)
+      { name: project.name }
+    end
+
+    def action_urls(project)
+      urls = action_issue_urls(project).merge(
+        issue_created: project_issues_path(project),
+        git_write: project_path(project),
+        merge_request_created: project_merge_requests_path(project),
+        user_added: project_members_url(project),
+        **deploy_section_action_urls(project)
+      )
+
+      return urls unless ::Gitlab::CurrentSettings.should_check_namespace_plan?
+
+      trial_items = {
+        trial_started: project_project_members_path(project),
+        required_mr_approvals_enabled: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
+        code_owners_enabled: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners')
+      }
+
+      if can_start_trial?(project)
+        trial_items = {
+          trial_started: new_trial_path_with_glm(content: ONBOARDING_START_TRIAL),
+          required_mr_approvals_enabled: new_trial_path_with_glm(content: ONBOARDING_REQUIRE_MR_APPROVALS),
+          code_owners_enabled: new_trial_path_with_glm(content: ONBOARDING_CODE_OWNERS)
+        }
+      end
+
+      urls.merge(trial_items)
+    end
+
+    def new_trial_path_with_glm(content:, source: GITLAB_COM)
+      new_trial_path({ glm_source: source, glm_content: content })
+    end
+
+    def action_issue_urls(project)
+      Onboarding::Completion::ACTION_ISSUE_IDS.transform_values do |id|
+        project_issue_url(project, id)
+      end
+    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
+  end
+end
diff --git a/app/models/onboarding/learn_gitlab.rb b/ee/app/models/onboarding/learn_gitlab.rb
similarity index 72%
rename from app/models/onboarding/learn_gitlab.rb
rename to ee/app/models/onboarding/learn_gitlab.rb
index d7a189ed6e28..8b7d0770d749 100644
--- a/app/models/onboarding/learn_gitlab.rb
+++ b/ee/app/models/onboarding/learn_gitlab.rb
@@ -6,13 +6,16 @@ class LearnGitlab
     PROJECT_NAME_ULTIMATE_TRIAL = 'Learn GitLab - Ultimate trial'
     BOARD_NAME = 'GitLab onboarding'
     LABEL_NAME = 'Novice'
+    TEMPLATE_NAME = 'learn_gitlab_ultimate.tar.gz'
 
     def initialize(current_user)
       @current_user = current_user
     end
 
-    def available?
-      project && board && label
+    def onboarding_and_available?(namespace)
+      return false unless current_user && project
+
+      Onboarding::Progress.onboarding?(namespace) && available?
     end
 
     def project
@@ -34,5 +37,9 @@ def label
     private
 
     attr_reader :current_user
+
+    def available?
+      project.present? && board.present? && label.present?
+    end
   end
 end
diff --git a/ee/app/services/registrations/standard_namespace_create_service.rb b/ee/app/services/registrations/standard_namespace_create_service.rb
index 50cd1cb233aa..cd7aad865935 100644
--- a/ee/app/services/registrations/standard_namespace_create_service.rb
+++ b/ee/app/services/registrations/standard_namespace_create_service.rb
@@ -14,8 +14,6 @@ def execute
 
     private
 
-    LEARN_GITLAB_ULTIMATE_TEMPLATE = 'learn_gitlab_ultimate.tar.gz'
-
     def new_group?
       !existing_group_id
     end
@@ -76,7 +74,7 @@ def create_learn_gitlab_project
     end
 
     def learn_gitlab_template_path
-      Rails.root.join('vendor', 'project_templates', LEARN_GITLAB_ULTIMATE_TEMPLATE)
+      Rails.root.join('vendor', 'project_templates', Onboarding::LearnGitlab::TEMPLATE_NAME)
     end
 
     def learn_gitlab_project_name
diff --git a/app/views/projects/learn_gitlab/index.html.haml b/ee/app/views/projects/learn_gitlab/index.html.haml
similarity index 71%
rename from app/views/projects/learn_gitlab/index.html.haml
rename to ee/app/views/projects/learn_gitlab/index.html.haml
index 0e950c26d34d..b5e9ffd000ec 100644
--- a/app/views/projects/learn_gitlab/index.html.haml
+++ b/ee/app/views/projects/learn_gitlab/index.html.haml
@@ -1,8 +1,7 @@
 - breadcrumb_title _("Learn GitLab")
 - page_title _("Learn GitLab")
 - add_page_specific_style 'page_bundles/learn_gitlab'
-- data = learn_gitlab_data(@project)
 
 = render 'projects/invite_members_modal', project: @project
 
-#js-learn-gitlab-app{ data: data }
+#js-learn-gitlab-app{ data: learn_gitlab_data(@project) }
diff --git a/ee/app/views/registrations/welcome/continuous_onboarding_getting_started.html.haml b/ee/app/views/registrations/welcome/continuous_onboarding_getting_started.html.haml
index 913ca8aaccd5..7f110d8e8c5a 100644
--- a/ee/app/views/registrations/welcome/continuous_onboarding_getting_started.html.haml
+++ b/ee/app/views/registrations/welcome/continuous_onboarding_getting_started.html.haml
@@ -1,6 +1,5 @@
-- return unless project
 - page_title _('Get started with GitLab')
-- unless learn_gitlab_onboarding_available?(project)
+- unless learn_gitlab_onboarding_available?(project.namespace)
   = content_for :meta_tags do
     %meta{ 'http-equiv': 'refresh', content: '5' }
 - content_for :page_specific_javascripts do
@@ -13,7 +12,7 @@
       %h2.gl-text-center.gl-my-5= _('Get started with GitLab')
       %p.gl-text-center.gl-text-gray-700.gl-mb-7= _('Ready to get started with GitLab? Follow these steps to set up your workspace, plan and commit changes, and deploy your project.')
       = image_tag 'learn_gitlab/get_started.svg', width: '400', class: 'gl-max-w-full'
-      - if learn_gitlab_onboarding_available?(project)
+      - if learn_gitlab_onboarding_available?(project.namespace)
         %div= link_to s_("LearnGitlab|Ok, let's go"), project_learn_gitlab_path(project), class: 'btn btn-confirm gl-button gl-mt-7 js-get-started-btn', data: { qa_selector: 'confirm_button', track_label: track_label, track_action: "click_ok_lets_go" }
       - else
         %p.gl-text-center.gl-text-gray-700.gl-mt-7= s_('LearnGitlab|Creating your onboarding experience...')
diff --git a/config/feature_flags/experiment/invite_for_help_continuous_onboarding.yml b/ee/config/feature_flags/experiment/invite_for_help_continuous_onboarding.yml
similarity index 100%
rename from config/feature_flags/experiment/invite_for_help_continuous_onboarding.yml
rename to ee/config/feature_flags/experiment/invite_for_help_continuous_onboarding.yml
diff --git a/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml b/ee/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml
similarity index 100%
rename from config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml
rename to ee/config/feature_flags/experiment/video_tutorials_continuous_onboarding.yml
diff --git a/ee/config/routes/project.rb b/ee/config/routes/project.rb
index 2ad17e20fcba..8d7e56068b26 100644
--- a/ee/config/routes/project.rb
+++ b/ee/config/routes/project.rb
@@ -40,6 +40,8 @@
 
         resources :subscriptions, only: [:create, :destroy]
 
+        get :learn_gitlab, action: :index, controller: 'learn_gitlab'
+
         resources :protected_environments, only: [:create, :update, :destroy], constraints: { id: /\d+/ } do
           collection do
             get 'search'
diff --git a/ee/lib/ee/sidebars/projects/panel.rb b/ee/lib/ee/sidebars/projects/panel.rb
index b49161bf366b..c0377e2543ae 100644
--- a/ee/lib/ee/sidebars/projects/panel.rb
+++ b/ee/lib/ee/sidebars/projects/panel.rb
@@ -10,8 +10,14 @@ module Panel
         def configure_menus
           super
 
-          insert_menu_before(::Sidebars::Projects::Menus::ProjectInformationMenu,
-                             ::Sidebars::Projects::Menus::TrialWidgetMenu.new(context))
+          insert_menu_before(
+            ::Sidebars::Projects::Menus::ProjectInformationMenu,
+            ::Sidebars::Projects::Menus::TrialWidgetMenu.new(context)
+          )
+          insert_menu_after(
+            ::Sidebars::Projects::Menus::ProjectInformationMenu,
+            ::Sidebars::Projects::Menus::LearnGitlabMenu.new(context)
+          )
 
           if ::Sidebars::Projects::Menus::IssuesMenu.new(context).show_jira_menu_items?
             remove_menu(::Sidebars::Projects::Menus::ExternalIssueTrackerMenu)
diff --git a/lib/sidebars/projects/menus/learn_gitlab_menu.rb b/ee/lib/sidebars/projects/menus/learn_gitlab_menu.rb
similarity index 81%
rename from lib/sidebars/projects/menus/learn_gitlab_menu.rb
rename to ee/lib/sidebars/projects/menus/learn_gitlab_menu.rb
index b6fae2af93d8..e4178292cfee 100644
--- a/lib/sidebars/projects/menus/learn_gitlab_menu.rb
+++ b/ee/lib/sidebars/projects/menus/learn_gitlab_menu.rb
@@ -28,14 +28,12 @@ def has_pill?
 
         override :pill_count
         def pill_count
-          strong_memoize(:pill_count) do
-            percentage = Onboarding::Completion.new(
-              context.project.namespace,
-              context.current_user
-            ).percentage
-
-            "#{percentage}%"
-          end
+          percentage = Onboarding::Completion.new(
+            context.project.namespace,
+            context.current_user
+          ).percentage
+
+          "#{percentage}%"
         end
 
         override :extra_nav_link_html_options
diff --git a/spec/controllers/projects/learn_gitlab_controller_spec.rb b/ee/spec/controllers/projects/learn_gitlab_controller_spec.rb
similarity index 70%
rename from spec/controllers/projects/learn_gitlab_controller_spec.rb
rename to ee/spec/controllers/projects/learn_gitlab_controller_spec.rb
index bcc8fdc0265d..51657bb45e9d 100644
--- a/spec/controllers/projects/learn_gitlab_controller_spec.rb
+++ b/ee/spec/controllers/projects/learn_gitlab_controller_spec.rb
@@ -5,16 +5,23 @@
 RSpec.describe Projects::LearnGitlabController, feature_category: :onboarding do
   describe 'GET #index' do
     let_it_be(:user) { create(:user) }
+    let_it_be(:learn_gitlab_project) do
+      create(:project, name: Onboarding::LearnGitlab::PROJECT_NAME).tap do |record|
+        record.add_maintainer(user)
+      end
+    end
+
     let_it_be(:project) { create(:project, namespace: create(:group)) }
+    let_it_be(:board) { create(:board, project: learn_gitlab_project, name: Onboarding::LearnGitlab::BOARD_NAME) }
 
-    let(:learn_gitlab_enabled) { true }
     let(:params) { { namespace_id: project.namespace.to_param, project_id: project } }
 
     subject(:action) { get :index, params: params }
 
-    before do
+    before_all do
       project.namespace.add_owner(user)
-      allow(controller.helpers).to receive(:learn_gitlab_enabled?).and_return(learn_gitlab_enabled)
+      create(:label, project: learn_gitlab_project, name: Onboarding::LearnGitlab::LABEL_NAME)
+      create(:onboarding_progress, namespace: project.namespace)
     end
 
     context 'for unauthenticated user' do
@@ -29,7 +36,9 @@
       it { is_expected.to render_template(:index) }
 
       context 'when learn_gitlab is not available' do
-        let(:learn_gitlab_enabled) { false }
+        before do
+          board.update!(name: 'bogus')
+        end
 
         it { is_expected.to have_gitlab_http_status(:not_found) }
       end
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap b/ee/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
similarity index 100%
rename from spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_section_card_spec.js.snap
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/ee/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
similarity index 100%
rename from spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
similarity index 84%
rename from spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
index 3a511a009a98..be28aedf166f 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
+++ b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_card_spec.js
@@ -1,5 +1,5 @@
 import { shallowMount } from '@vue/test-utils';
-import LearnGitlabSectionCard from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue';
+import LearnGitlabSectionCard from 'ee/pages/projects/learn_gitlab/components/learn_gitlab_section_card.vue';
 import { testActions } from './mock_data';
 
 const defaultSection = 'workspace';
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
similarity index 97%
rename from spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
index 293353083701..c22aa6a955cb 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
+++ b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js
@@ -4,8 +4,8 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
 import { stubExperiments } from 'helpers/experimentation_helper';
 import { mockTracking, triggerEvent, unmockTracking } from 'helpers/tracking_helper';
 import eventHub from '~/invite_members/event_hub';
-import LearnGitlabSectionLink from '~/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue';
-import { ACTION_LABELS } from '~/pages/projects/learn_gitlab/constants';
+import LearnGitlabSectionLink from 'ee/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue';
+import { ACTION_LABELS } from 'ee/pages/projects/learn_gitlab/constants';
 
 const defaultAction = 'gitWrite';
 const defaultProps = {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
similarity index 94%
rename from spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
index 0f63c243342c..57414e2e1eac 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
+++ b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_spec.js
@@ -1,9 +1,9 @@
 import { GlProgressBar, GlAlert } from '@gitlab/ui';
 import { mount } from '@vue/test-utils';
 import Cookies from '~/lib/utils/cookies';
-import LearnGitlab from '~/pages/projects/learn_gitlab/components/learn_gitlab.vue';
+import LearnGitlab from 'ee/pages/projects/learn_gitlab/components/learn_gitlab.vue';
 import eventHub from '~/invite_members/event_hub';
-import { INVITE_MODAL_OPEN_COOKIE } from '~/pages/projects/learn_gitlab/constants';
+import { INVITE_MODAL_OPEN_COOKIE } from 'ee/pages/projects/learn_gitlab/constants';
 import { testActions, testSections, testProject } from './mock_data';
 
 describe('Learn GitLab', () => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js
similarity index 71%
rename from spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js
index 6ab57e31fed3..7dfc7b6cce59 100644
--- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js
+++ b/ee/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_trial_card_spec.js
@@ -1,5 +1,5 @@
 import { shallowMount } from '@vue/test-utils';
-import IncludedInTrialIndicator from '~/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue';
+import IncludedInTrialIndicator from 'ee/pages/projects/learn_gitlab/components/included_in_trial_indicator.vue';
 
 describe('Learn GitLab Trial Card', () => {
   it('renders correctly', () => {
diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/ee/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
similarity index 100%
rename from spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
rename to ee/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js
diff --git a/ee/spec/helpers/ee/invite_members_helper_spec.rb b/ee/spec/helpers/ee/invite_members_helper_spec.rb
index 0ce4c14f49ec..ff0912232b57 100644
--- a/ee/spec/helpers/ee/invite_members_helper_spec.rb
+++ b/ee/spec/helpers/ee/invite_members_helper_spec.rb
@@ -133,6 +133,66 @@
         end
       end
     end
+
+    context 'with tasks_to_be_done' do
+      using RSpec::Parameterized::TableSyntax
+
+      let_it_be(:project) { create(:project) }
+      let_it_be(:group) { create(:group, projects: [project]) }
+      let_it_be(:developer) { create(:user, developer_projects: [project]) }
+
+      subject(:output) { helper.common_invite_modal_dataset(source) }
+
+      shared_examples_for 'including the tasks to be done attributes' do
+        it 'includes the tasks to be done attributes when expected' do
+          if expected?
+            result = [
+              { value: :code, text: 'Create/import code into a project (repository)' },
+              { value: :ci, text: 'Set up CI/CD pipelines to build, test, deploy, and monitor code' },
+              { value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
+            ].to_json
+
+            expect(output[:tasks_to_be_done_options]).to eq(result)
+            expect(output[:projects]).to eq([{ id: project.id, title: project.title }].to_json)
+            expect(output[:new_project_path])
+              .to eq(source.is_a?(Project) ? '' : new_project_path(namespace_id: group.id))
+          else
+            expect(output[:tasks_to_be_done_options]).to be_nil
+            expect(output[:projects]).to be_nil
+            expect(output[:new_project_path]).to be_nil
+          end
+        end
+      end
+
+      context 'with the invite_for_help_continuous_onboarding experiment' do
+        where(:invite_continuous_onboarding?, :logged_in?, :expected?) do
+          true  | true  | true
+          true  | false | false
+          false | true  | false
+          false | false | false
+        end
+
+        with_them do
+          before do
+            allow(helper).to receive(:current_user).and_return(developer) if logged_in?
+            stub_experiments(invite_for_help_continuous_onboarding: :candidate) if invite_continuous_onboarding?
+          end
+
+          context 'when the source is a project' do
+            let(:source) { project }
+
+            it_behaves_like 'including the tasks to be done attributes'
+          end
+
+          context 'when the source is a group' do
+            let(:source) { group }
+            let(:expected?) { false }
+
+            it_behaves_like 'including the tasks to be done attributes'
+          end
+        end
+      end
+    end
   end
 
   describe '#users_filter_data' do
diff --git a/ee/spec/helpers/ee/learn_gitlab_helper_spec.rb b/ee/spec/helpers/ee/learn_gitlab_helper_spec.rb
deleted file mode 100644
index 994bb472a2a1..000000000000
--- a/ee/spec/helpers/ee/learn_gitlab_helper_spec.rb
+++ /dev/null
@@ -1,144 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe LearnGitlabHelper do
-  include Devise::Test::ControllerHelpers
-
-  let_it_be(:user) { create(:user) }
-  let_it_be(:project) { create(:project, name: Onboarding::LearnGitlab::PROJECT_NAME, namespace: user.namespace) }
-  let_it_be(:namespace) { project.namespace }
-  let(:disabled_message) { s_('LearnGitlab|Contact your administrator to start a free Ultimate trial.') }
-
-  before do
-    allow_next_instance_of(Onboarding::LearnGitlab) do |learn_gitlab|
-      allow(learn_gitlab).to receive(:project).and_return(project)
-    end
-
-    Onboarding::Progress.onboard(namespace)
-    sign_in(user)
-  end
-
-  describe '#learn_gitlab_data' do
-    subject(:onboarding_actions_data) do
-      Gitlab::Json.parse(helper.learn_gitlab_data(project)[:actions]).deep_symbolize_keys
-    end
-
-    context 'when in the new action URLs experiment' do
-      context 'for trial- and subscription-related actions' do
-        context 'when namespace plans are not enabled' do
-          before do
-            stub_application_setting(check_namespace_plan: false)
-          end
-
-          it 'provides the default URLs' do
-            expect(onboarding_actions_data).to include(
-              trial_started: a_hash_including(
-                url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/2})
-              ),
-              code_owners_enabled: a_hash_including(
-                url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/10})
-              ),
-              required_mr_approvals_enabled: a_hash_including(
-                url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/11})
-              )
-            )
-          end
-        end
-
-        context 'when namespace plans are enabled' do
-          before do
-            stub_application_setting(check_namespace_plan: true)
-          end
-
-          context 'when namespace has free or no subscription' do
-            before do
-              allow(namespace).to receive(:has_free_or_no_subscription?).and_return(true)
-            end
-
-            it 'provides URLs to start a trial to namespace admins' do
-              allow(helper).to receive(:can?).with(user, :admin_namespace, namespace).and_return(true)
-              expect(onboarding_actions_data).to include(
-                trial_started: a_hash_including(
-                  url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-start-trial'),
-                  enabled: true
-                ),
-                code_owners_enabled: a_hash_including(
-                  url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-code-owners'),
-                  enabled: true
-                ),
-                required_mr_approvals_enabled: a_hash_including(
-                  url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-require-merge-approvals'),
-                  enabled: true
-                )
-              )
-            end
-
-            it 'provides URLs to Gitlab docs to namespace non-admins' do
-              allow(helper).to receive(:can?).with(user, :admin_namespace, namespace).and_return(false)
-              expect(onboarding_actions_data).to include(
-                trial_started: a_hash_including(
-                  url: project_project_members_path(project),
-                  enabled: false,
-                  message: disabled_message
-                ),
-                code_owners_enabled: a_hash_including(
-                  url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
-                  enabled: true
-                ),
-                required_mr_approvals_enabled: a_hash_including(
-                  url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
-                  enabled: true
-                )
-              )
-            end
-          end
-
-          context 'when namespace has paid subscription' do
-            before do
-              allow(namespace).to receive(:has_free_or_no_subscription?).and_return(false)
-            end
-
-            it 'provides URLs to Gitlab docs to namespace admins' do
-              allow(helper).to receive(:can?).with(user, :admin_namespace, namespace).and_return(true)
-              expect(onboarding_actions_data).to include(
-                trial_started: a_hash_including(
-                  url: project_project_members_path(project),
-                  enabled: false,
-                  message: disabled_message
-                ),
-                code_owners_enabled: a_hash_including(
-                  url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
-                  enabled: true
-                ),
-                required_mr_approvals_enabled: a_hash_including(
-                  url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
-                  enabled: true
-                )
-              )
-            end
-
-            it 'provides URLs to Gitlab docs to namespace non-admins' do
-              allow(helper).to receive(:can?).with(user, :admin_namespace, namespace).and_return(false)
-              expect(onboarding_actions_data).to include(
-                trial_started: a_hash_including(
-                  url: project_project_members_path(project),
-                  enabled: false,
-                  message: disabled_message
-                ),
-                code_owners_enabled: a_hash_including(
-                  url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
-                  enabled: true
-                ),
-                required_mr_approvals_enabled: a_hash_including(
-                  url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
-                  enabled: true
-                )
-              )
-            end
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/ee/spec/helpers/projects/learn_gitlab_helper_spec.rb b/ee/spec/helpers/projects/learn_gitlab_helper_spec.rb
new file mode 100644
index 000000000000..04e4ef8bd50a
--- /dev/null
+++ b/ee/spec/helpers/projects/learn_gitlab_helper_spec.rb
@@ -0,0 +1,266 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Projects::LearnGitlabHelper, feature_category: :onboarding do
+  let_it_be(:user) { create(:user) }
+
+  before do
+    allow(helper).to receive(:current_user).and_return(user)
+  end
+
+  describe '#learn_gitlab_data' do
+    let_it_be(:namespace) { create(:group) }
+    let_it_be(:project) { build_stubbed(:project, name: Onboarding::LearnGitlab::PROJECT_NAME, namespace: namespace) }
+    let(:onboarding_actions_data) { Gitlab::Json.parse(learn_gitlab_data[:actions]).deep_symbolize_keys }
+    let(:onboarding_sections_data) { Gitlab::Json.parse(learn_gitlab_data[:sections]).deep_symbolize_keys }
+    let(:onboarding_project_data) { Gitlab::Json.parse(learn_gitlab_data[:project]).deep_symbolize_keys }
+
+    before do
+      Onboarding::Progress.onboard(namespace)
+      Onboarding::Progress.register(namespace, :git_write)
+    end
+
+    subject(:learn_gitlab_data) { helper.learn_gitlab_data(project) }
+
+    shared_examples 'has all data' do
+      it 'has all actions' do
+        expected_keys = [
+          :issue_created,
+          :git_write,
+          :pipeline_created,
+          :merge_request_created,
+          :user_added,
+          :trial_started,
+          :required_mr_approvals_enabled,
+          :code_owners_enabled,
+          :security_scan_enabled
+        ]
+
+        expect(onboarding_actions_data.keys).to contain_exactly(*expected_keys)
+      end
+
+      it 'has all section data', :aggregate_failures do
+        expect(onboarding_sections_data.keys).to contain_exactly(:deploy, :plan, :workspace)
+        expect(onboarding_sections_data.values.map(&:keys)).to match_array([[:svg]] * 3)
+      end
+
+      it 'has all project data', :aggregate_failures do
+        expect(onboarding_project_data.keys).to contain_exactly(:name)
+        expect(onboarding_project_data.values).to match_array([project.name])
+      end
+    end
+
+    it_behaves_like 'has all data'
+
+    it 'sets correct completion statuses' do
+      result = {
+        issue_created: a_hash_including(completed: false),
+        git_write: a_hash_including(completed: true),
+        pipeline_created: a_hash_including(completed: false),
+        merge_request_created: a_hash_including(completed: false),
+        user_added: a_hash_including(completed: false),
+        trial_started: a_hash_including(completed: false),
+        required_mr_approvals_enabled: a_hash_including(completed: false),
+        code_owners_enabled: a_hash_including(completed: false),
+        security_scan_enabled: a_hash_including(completed: false)
+      }
+
+      expect(onboarding_actions_data).to match(result)
+    end
+
+    context 'with 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
+          result = base_paths.merge(
+            security_scan_enabled: a_hash_including(
+              url: %r{/learn_gitlab/-/security/configuration\z}
+            )
+          )
+
+          expect(onboarding_actions_data).to match(result)
+        end
+      end
+
+      context 'when candidate' do
+        before do
+          stub_experiments(security_actions_continuous_onboarding: :candidate)
+        end
+
+        it 'sets correct paths' do
+          result = 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')
+            )
+          )
+
+          expect(onboarding_actions_data).to match(result)
+        end
+      end
+    end
+
+    context 'for trial- and subscription-related actions' do
+      let(:disabled_message) { s_('LearnGitlab|Contact your administrator to start a free Ultimate trial.') }
+
+      context 'when namespace plans are not enabled' do
+        before do
+          stub_application_setting(check_namespace_plan: false)
+        end
+
+        it 'provides the default URLs' do
+          result = {
+            trial_started: a_hash_including(
+              url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/2})
+            ),
+            code_owners_enabled: a_hash_including(
+              url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/10})
+            ),
+            required_mr_approvals_enabled: a_hash_including(
+              url: a_string_matching(%r{#{namespace.path}/learn_gitlab/-/issues/11})
+            )
+          }
+
+          expect(onboarding_actions_data).to include(result)
+        end
+      end
+
+      context 'when namespace plans are enabled' do
+        before do
+          stub_application_setting(check_namespace_plan: true)
+        end
+
+        context 'when namespace has free or no subscription' do
+          before do
+            allow(namespace).to receive(:has_free_or_no_subscription?).and_return(true)
+          end
+
+          it 'provides URLs to start a trial to namespace admins' do
+            namespace.add_owner(user)
+            result = {
+              trial_started: a_hash_including(
+                url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-start-trial'),
+                enabled: true
+              ),
+              code_owners_enabled: a_hash_including(
+                url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-code-owners'),
+                enabled: true
+              ),
+              required_mr_approvals_enabled: a_hash_including(
+                url: new_trial_path(glm_source: 'gitlab.com', glm_content: 'onboarding-require-merge-approvals'),
+                enabled: true
+              )
+            }
+
+            expect(onboarding_actions_data).to include(result)
+          end
+
+          it 'provides URLs to Gitlab docs to namespace non-admins' do
+            result = {
+              trial_started: a_hash_including(
+                url: project_project_members_path(project),
+                enabled: false,
+                message: disabled_message
+              ),
+              code_owners_enabled: a_hash_including(
+                url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
+                enabled: true
+              ),
+              required_mr_approvals_enabled: a_hash_including(
+                url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
+                enabled: true
+              )
+            }
+
+            expect(onboarding_actions_data).to include(result)
+          end
+        end
+
+        context 'when namespace has paid subscription' do
+          before do
+            allow(namespace).to receive(:has_free_or_no_subscription?).and_return(false)
+          end
+
+          it 'provides URLs to Gitlab docs to namespace admins' do
+            namespace.add_owner(user)
+            result = {
+              trial_started: a_hash_including(
+                url: project_project_members_path(project),
+                enabled: false,
+                message: disabled_message
+              ),
+              code_owners_enabled: a_hash_including(
+                url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
+                enabled: true
+              ),
+              required_mr_approvals_enabled: a_hash_including(
+                url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
+                enabled: true
+              )
+            }
+
+            expect(onboarding_actions_data).to include(result)
+          end
+
+          it 'provides URLs to Gitlab docs to namespace non-admins' do
+            result = {
+              trial_started: a_hash_including(
+                url: project_project_members_path(project),
+                enabled: false,
+                message: disabled_message
+              ),
+              code_owners_enabled: a_hash_including(
+                url: help_page_path('user/project/code_owners', anchor: 'set-up-code-owners'),
+                enabled: true
+              ),
+              required_mr_approvals_enabled: a_hash_including(
+                url: help_page_path('ci/pipelines/settings', anchor: 'coverage-check-approval-rule'),
+                enabled: true
+              )
+            }
+
+            expect(onboarding_actions_data).to include(result)
+          end
+        end
+      end
+    end
+  end
+
+  describe '#learn_gitlab_onboarding_available?' do
+    let(:namespace) { build(:group) }
+
+    it 'is not available' do
+      expect(helper.learn_gitlab_onboarding_available?(namespace)).to eq(false)
+    end
+
+    it 'is available' do
+      allow_next_instance_of(Onboarding::LearnGitlab, user) do |instance|
+        allow(instance).to receive(:onboarding_and_available?).with(namespace).and_return(true)
+      end
+
+      expect(helper.learn_gitlab_onboarding_available?(namespace)).to eq(true)
+    end
+  end
+end
diff --git a/ee/spec/lib/ee/sidebars/projects/panel_spec.rb b/ee/spec/lib/ee/sidebars/projects/panel_spec.rb
index 4ac5f0e0bb14..35ce79095338 100644
--- a/ee/spec/lib/ee/sidebars/projects/panel_spec.rb
+++ b/ee/spec/lib/ee/sidebars/projects/panel_spec.rb
@@ -37,6 +37,12 @@ def contains_external_issue_tracker_menu?
     end
   end
 
+  context 'with learn gitlab menu' do
+    it 'contains the menu' do
+      expect(contains_menu?(Sidebars::Projects::Menus::LearnGitlabMenu)).to be(true)
+    end
+  end
+
   def contains_menu?(menu)
     panel.instance_variable_get(:@menus).any? { |i| i.is_a?(menu) }
   end
diff --git a/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb b/ee/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb
similarity index 77%
rename from spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb
rename to ee/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb
index 4ae29f28f3a9..6efadc6e0252 100644
--- a/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb
+++ b/ee/spec/lib/sidebars/projects/menus/learn_gitlab_menu_spec.rb
@@ -2,10 +2,9 @@
 
 require 'spec_helper'
 
-RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu do
-  let_it_be(:project) { build(:project) }
-  let_it_be(:learn_gitlab_enabled) { true }
-
+RSpec.describe Sidebars::Projects::Menus::LearnGitlabMenu, feature_category: :onboarding do
+  let(:project) { build(:project) }
+  let(:learn_gitlab_enabled) { true }
   let(:context) do
     Sidebars::Projects::Context.new(
       current_user: nil,
@@ -21,7 +20,7 @@
   end
 
   describe '#nav_link_html_options' do
-    let_it_be(:data_tracking) do
+    let(:data_tracking) do
       {
         class: 'home',
         data: {
@@ -36,13 +35,13 @@
   end
 
   describe '#render?' do
-    context 'when learn gitlab experiment is enabled' do
+    context 'when learn gitlab is enabled' do
       it 'returns true' do
         expect(subject.render?).to eq true
       end
     end
 
-    context 'when learn gitlab experiment is disabled' do
+    context 'when learn gitlab is disabled' do
       let(:learn_gitlab_enabled) { false }
 
       it 'returns false' do
@@ -52,13 +51,13 @@
   end
 
   describe '#has_pill?' do
-    context 'when learn gitlab experiment is enabled' do
+    context 'when learn gitlab is enabled' do
       it 'returns true' do
         expect(subject.has_pill?).to eq true
       end
     end
 
-    context 'when learn gitlab experiment is disabled' do
+    context 'when learn gitlab is disabled' do
       let(:learn_gitlab_enabled) { false }
 
       it 'returns false' do
diff --git a/spec/models/onboarding/learn_gitlab_spec.rb b/ee/spec/models/onboarding/learn_gitlab_spec.rb
similarity index 54%
rename from spec/models/onboarding/learn_gitlab_spec.rb
rename to ee/spec/models/onboarding/learn_gitlab_spec.rb
index 5e3e1f9c304e..25c26cf00acc 100644
--- a/spec/models/onboarding/learn_gitlab_spec.rb
+++ b/ee/spec/models/onboarding/learn_gitlab_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Onboarding::LearnGitlab do
+RSpec.describe Onboarding::LearnGitlab, feature_category: :onboarding do
   let_it_be(:current_user) { create(:user) }
   let_it_be(:learn_gitlab_project) { create(:project, name: described_class::PROJECT_NAME) }
   let_it_be(:learn_gitlab_board) { create(:board, project: learn_gitlab_project, name: described_class::BOARD_NAME) }
@@ -12,35 +12,6 @@
     learn_gitlab_project.add_developer(current_user)
   end
 
-  describe '#available?' do
-    using RSpec::Parameterized::TableSyntax
-
-    where(:project, :board, :label, :expected_result) do
-      nil  | nil  | nil  | nil
-      nil  | nil  | true | nil
-      nil  | true | nil  | nil
-      nil  | true | true | nil
-      true | nil  | nil  | nil
-      true | nil  | true | nil
-      true | true | nil  | nil
-      true | true | true | true
-    end
-
-    with_them do
-      before do
-        allow_next_instance_of(described_class) do |learn_gitlab|
-          allow(learn_gitlab).to receive(:project).and_return(project)
-          allow(learn_gitlab).to receive(:board).and_return(board)
-          allow(learn_gitlab).to receive(:label).and_return(label)
-        end
-      end
-
-      subject { described_class.new(current_user).available? }
-
-      it { is_expected.to be expected_result }
-    end
-  end
-
   describe '#project' do
     subject { described_class.new(current_user).project }
 
@@ -66,4 +37,45 @@
 
     it { is_expected.to eq learn_gitlab_label }
   end
+
+  describe '#onboarding_and_available?' do
+    using RSpec::Parameterized::TableSyntax
+
+    let(:namespace) { build(:namespace) }
+
+    where(:current_user, :project, :available, :onboarding, :expected_result) do
+      nil  | nil  | false | false | false
+      true | nil  | false | false | false
+      true | true | false | false | false
+      true | true | true  | false | false
+      true | true | true  | true  | true
+      nil  | nil  | false | true  | false
+      nil  | nil  | true  | true  | false
+      nil  | nil  | true  | false | false
+      nil  | true | true  | true  | false
+      nil  | true | true  | false | false
+      nil  | true | false | false | false
+      nil  | true | false | true  | false
+      true | nil  | true  | true  | false
+      true | true | false | true  | false
+      true | nil  | true  | false | false
+      true | nil  | false | true  | false
+    end
+
+    with_them do
+      before do
+        allow_next_instance_of(described_class) do |learn_gitlab|
+          allow(learn_gitlab).to receive(:project).and_return(project)
+          allow(learn_gitlab).to receive(:available?).and_return(available)
+          allow(learn_gitlab).to receive(:current_user).and_return(current_user)
+        end
+
+        allow(Onboarding::Progress).to receive(:onboarding?).with(namespace).and_return(onboarding)
+      end
+
+      subject { described_class.new(current_user).onboarding_and_available?(namespace) }
+
+      it { is_expected.to be expected_result }
+    end
+  end
 end
diff --git a/ee/spec/services/registrations/standard_namespace_create_service_spec.rb b/ee/spec/services/registrations/standard_namespace_create_service_spec.rb
index e0ca4d83f2c4..672d846be12a 100644
--- a/ee/spec/services/registrations/standard_namespace_create_service_spec.rb
+++ b/ee/spec/services/registrations/standard_namespace_create_service_spec.rb
@@ -173,8 +173,8 @@
 
     context 'with learn gitlab project', :sidekiq_inline do
       where(:trial, :project_name, :template) do
-        'false' | 'Learn GitLab'                  | described_class::LEARN_GITLAB_ULTIMATE_TEMPLATE
-        'true'  | 'Learn GitLab - Ultimate trial' | described_class::LEARN_GITLAB_ULTIMATE_TEMPLATE
+        'false' | 'Learn GitLab'                  | Onboarding::LearnGitlab::TEMPLATE_NAME
+        'true'  | 'Learn GitLab - Ultimate trial' | Onboarding::LearnGitlab::TEMPLATE_NAME
       end
 
       with_them do
diff --git a/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 556364548293..80e590b2ff5e 100644
--- a/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe 'layouts/nav/sidebar/_project' do
+RSpec.describe 'layouts/nav/sidebar/_project', feature_category: :navigation do
   let_it_be_with_refind(:project) { create(:project, :repository) }
   let_it_be(:user) { project.first_owner }
 
@@ -13,6 +13,22 @@
     allow(view).to receive(:current_ref).and_return('master')
   end
 
+  describe 'Learn GitLab' do
+    it 'has a link to the learn GitLab' do
+      allow(view).to receive(:current_user).and_return(user)
+      allow_next_instance_of(Onboarding::LearnGitlab, user) do |instance|
+        allow(instance).to receive(:onboarding_and_available?).with(project.namespace).and_return(true)
+      end
+      allow_next_instance_of(Onboarding::Completion) do |onboarding|
+        expect(onboarding).to receive(:percentage).and_return(20)
+      end
+
+      render
+
+      expect(rendered).to have_link('Learn GitLab', href: project_learn_gitlab_path(project))
+    end
+  end
+
   describe 'Repository' do
     describe 'Files' do
       it 'has a link to the project file locks path' do
diff --git a/ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb b/ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb
index c2b9e7e3d424..eaf11088dec0 100644
--- a/ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb
+++ b/ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe 'registrations/welcome/continuous_onboarding_getting_started' do
+RSpec.describe 'registrations/welcome/continuous_onboarding_getting_started', feature_category: :onboarding do
   describe 'project import state' do
     let_it_be(:project) { create(:project) }
 
diff --git a/ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb b/ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb
index bfbbace23451..1c17ad7a3849 100644
--- a/ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb
+++ b/ee/spec/workers/onboarding/create_learn_gitlab_worker_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe Onboarding::CreateLearnGitlabWorker, type: :worker do
+RSpec.describe Onboarding::CreateLearnGitlabWorker, type: :worker, feature_category: :onboarding do
   include AfterNextHelpers
 
   let_it_be(:namespace) { create(:namespace) }
@@ -47,5 +47,22 @@
         perform
       end
     end
+
+    context 'when learn gitlab project already exists' do
+      let(:logger) { described_class.new.send(:logger) }
+      let(:project_name) { Onboarding::LearnGitlab::PROJECT_NAME }
+
+      before do
+        create(:project, name: project_name, namespace: namespace)
+      end
+
+      it 'invokes Projects::GitlabProjectsImportService' do
+        expect(File).not_to receive(:open).with(template_path)
+        expect_next(::Projects::GitlabProjectsImportService).not_to receive(:execute)
+        expect(logger).not_to receive(:error)
+
+        perform
+      end
+    end
   end
 end
diff --git a/lib/sidebars/projects/panel.rb b/lib/sidebars/projects/panel.rb
index 8ae8f931aab1..9d0f5eb87bd7 100644
--- a/lib/sidebars/projects/panel.rb
+++ b/lib/sidebars/projects/panel.rb
@@ -19,7 +19,6 @@ def aria_label
 
       def add_menus
         add_menu(Sidebars::Projects::Menus::ProjectInformationMenu.new(context))
-        add_menu(Sidebars::Projects::Menus::LearnGitlabMenu.new(context))
         add_menu(Sidebars::Projects::Menus::RepositoryMenu.new(context))
         add_menu(Sidebars::Projects::Menus::IssuesMenu.new(context))
         add_menu(Sidebars::Projects::Menus::ExternalIssueTrackerMenu.new(context))
diff --git a/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb b/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb
deleted file mode 100644
index 596791308a40..000000000000
--- a/spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe VideoTutorialsContinuousOnboardingExperiment do
-  it "defines a control and candidate" do
-    expect(subject.behaviors.keys).to match_array(%w[control candidate])
-  end
-end
diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb
index 48e94ec7e980..abf8b65dc1ec 100644
--- a/spec/helpers/invite_members_helper_spec.rb
+++ b/spec/helpers/invite_members_helper_spec.rb
@@ -36,7 +36,8 @@
       end
 
       it 'provides the correct attributes' do
-        expect(helper.common_invite_group_modal_data(group, GroupMember, 'false')).to include({ groups_filter: 'descendant_groups', parent_id: group.id })
+        expect(helper.common_invite_group_modal_data(group, GroupMember, 'false'))
+          .to include({ groups_filter: 'descendant_groups', parent_id: group.id })
       end
     end
 
@@ -46,7 +47,8 @@
       end
 
       it 'does not return filter attributes' do
-        expect(helper.common_invite_group_modal_data(project.group, ProjectMember, 'true').keys).not_to include(:groups_filter, :parent_id)
+        expect(helper.common_invite_group_modal_data(project.group, ProjectMember, 'true').keys)
+          .not_to include(:groups_filter, :parent_id)
       end
     end
   end
@@ -64,7 +66,7 @@
       expect(helper.common_invite_modal_dataset(project)).to include(attributes)
     end
 
-    context 'tasks_to_be_done' do
+    context 'with tasks_to_be_done' do
       using RSpec::Parameterized::TableSyntax
 
       subject(:output) { helper.common_invite_modal_dataset(source) }
@@ -79,9 +81,7 @@
                 { value: :issues, text: 'Create/import issues (tickets) to collaborate on ideas and plan work' }
               ].to_json
             )
-            expect(output[:projects]).to eq(
-              [{ id: project.id, title: project.title }].to_json
-            )
+            expect(output[:projects]).to eq([{ id: project.id, title: project.title }].to_json)
             expect(output[:new_project_path]).to eq(
               source.is_a?(Project) ? '' : new_project_path(namespace_id: group.id)
             )
@@ -93,8 +93,8 @@
         end
       end
 
-      context 'inviting members for tasks' do
-        where(:open_modal_param_present?, :logged_in?, :expected?) do
+      context 'when inviting members for tasks' do
+        where(:open_modal_param?, :logged_in?, :expected?) do
           true  | true  | true
           true  | false | false
           false | true  | false
@@ -104,7 +104,7 @@
         with_them do
           before do
             allow(helper).to receive(:current_user).and_return(developer) if logged_in?
-            allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param_present?
+            allow(helper).to receive(:params).and_return({ open_modal: 'invite_members_for_task' }) if open_modal_param?
           end
 
           context 'when the source is a project' do
@@ -120,36 +120,6 @@
           end
         end
       end
-
-      context 'the invite_for_help_continuous_onboarding experiment' do
-        where(:invite_for_help_continuous_onboarding?, :logged_in?, :expected?) do
-          true  | true  | true
-          true  | false | false
-          false | true  | false
-          false | false | false
-        end
-
-        with_them do
-          before do
-            allow(helper).to receive(:current_user).and_return(developer) if logged_in?
-            stub_experiments(invite_for_help_continuous_onboarding: :candidate) if invite_for_help_continuous_onboarding?
-          end
-
-          context 'when the source is a project' do
-            let_it_be(:source) { project }
-
-            it_behaves_like 'including the tasks to be done attributes'
-          end
-
-          context 'when the source is a group' do
-            let_it_be(:source) { group }
-
-            let(:expected?) { false }
-
-            it_behaves_like 'including the tasks to be done attributes'
-          end
-        end
-      end
     end
   end
 
@@ -172,11 +142,9 @@
       end
 
       context 'when the user can not manage project members' do
-        before do
+        it 'returns false' do
           expect(helper).to receive(:can?).with(owner, :admin_project_member, project).and_return(false)
-        end
 
-        it 'returns false' do
           expect(helper.can_invite_members_for_project?(project)).to eq false
         end
       end
diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb
deleted file mode 100644
index 0ec1434e4962..000000000000
--- a/spec/helpers/learn_gitlab_helper_spec.rb
+++ /dev/null
@@ -1,162 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe LearnGitlabHelper, feature_category: :onboarding do
-  include AfterNextHelpers
-  include Devise::Test::ControllerHelpers
-
-  let_it_be(:user) { create(:user) }
-  let_it_be(:project) { create(:project, name: Onboarding::LearnGitlab::PROJECT_NAME, namespace: user.namespace) }
-  let_it_be(:namespace) { project.namespace }
-
-  before do
-    allow_next_instance_of(Onboarding::LearnGitlab) do |learn_gitlab|
-      allow(learn_gitlab).to receive(:project).and_return(project)
-    end
-
-    Onboarding::Progress.onboard(namespace)
-    Onboarding::Progress.register(namespace, :git_write)
-  end
-
-  describe '#learn_gitlab_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_enabled?(project) }
-
-    where(:onboarding, :learn_gitlab_available, :result) do
-      true        | true                  | true
-      true        | false                 | false
-      false       | true                  | false
-    end
-
-    with_them do
-      before do
-        allow(Onboarding::Progress).to receive(:onboarding?).with(project.namespace).and_return(onboarding)
-        allow_next(Onboarding::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
-    end
-
-    context 'when not signed in' do
-      it { is_expected.to eq(false) }
-    end
-  end
-
-  describe '#learn_gitlab_data' do
-    subject(:learn_gitlab_data) { helper.learn_gitlab_data(project) }
-
-    let(:onboarding_actions_data) { Gitlab::Json.parse(learn_gitlab_data[:actions]).deep_symbolize_keys }
-    let(:onboarding_sections_data) { Gitlab::Json.parse(learn_gitlab_data[:sections]).deep_symbolize_keys }
-    let(:onboarding_project_data) { Gitlab::Json.parse(learn_gitlab_data[:project]).deep_symbolize_keys }
-
-    shared_examples 'has all data' do
-      it 'has all actions' do
-        expected_keys = [
-          :issue_created,
-          :git_write,
-          :pipeline_created,
-          :merge_request_created,
-          :user_added,
-          :trial_started,
-          :required_mr_approvals_enabled,
-          :code_owners_enabled,
-          :security_scan_enabled
-        ]
-
-        expect(onboarding_actions_data.keys).to contain_exactly(*expected_keys)
-      end
-
-      it 'has all section data', :aggregate_failures do
-        expect(onboarding_sections_data.keys).to contain_exactly(:deploy, :plan, :workspace)
-        expect(onboarding_sections_data.values.map(&:keys)).to match_array([[:svg]] * 3)
-      end
-
-      it 'has all project data', :aggregate_failures do
-        expect(onboarding_project_data.keys).to contain_exactly(:name)
-        expect(onboarding_project_data.values).to match_array([project.name])
-      end
-    end
-
-    it_behaves_like 'has all data'
-
-    it 'sets correct completion statuses' do
-      expect(onboarding_actions_data).to match({
-                                                 issue_created: a_hash_including(completed: false),
-                                                 git_write: a_hash_including(completed: true),
-                                                 pipeline_created: a_hash_including(completed: false),
-                                                 merge_request_created: a_hash_including(completed: false),
-                                                 user_added: a_hash_including(completed: false),
-                                                 trial_started: a_hash_including(completed: false),
-                                                 required_mr_approvals_enabled: a_hash_including(completed: false),
-                                                 code_owners_enabled: a_hash_including(completed: false),
-                                                 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/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index e50b08d8fbeb..420a704e20cd 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -966,11 +966,9 @@
 - './ee/spec/helpers/ee/groups/settings_helper_spec.rb'
 - './ee/spec/helpers/ee/hooks_helper_spec.rb'
 - './ee/spec/helpers/ee/integrations_helper_spec.rb'
-- './ee/spec/helpers/ee/invite_members_helper_spec.rb'
 - './ee/spec/helpers/ee/issuables_helper_spec.rb'
 - './ee/spec/helpers/ee/issues_helper_spec.rb'
 - './ee/spec/helpers/ee/labels_helper_spec.rb'
-- './ee/spec/helpers/ee/learn_gitlab_helper_spec.rb'
 - './ee/spec/helpers/ee/lock_helper_spec.rb'
 - './ee/spec/helpers/ee/namespaces_helper_spec.rb'
 - './ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb'
@@ -3633,7 +3631,6 @@
 - './spec/experiments/ios_specific_templates_experiment_spec.rb'
 - './spec/experiments/require_verification_for_namespace_creation_experiment_spec.rb'
 - './spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb'
-- './spec/experiments/video_tutorials_continuous_onboarding_experiment_spec.rb'
 - './spec/features/abuse_report_spec.rb'
 - './spec/features/action_cable_logging_spec.rb'
 - './spec/features/admin/admin_abuse_reports_spec.rb'
@@ -5160,7 +5157,6 @@
 - './spec/helpers/import_helper_spec.rb'
 - './spec/helpers/instance_configuration_helper_spec.rb'
 - './spec/helpers/integrations_helper_spec.rb'
-- './spec/helpers/invite_members_helper_spec.rb'
 - './spec/helpers/issuables_description_templates_helper_spec.rb'
 - './spec/helpers/issuables_helper_spec.rb'
 - './spec/helpers/issues_helper_spec.rb'
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 4de2c011b93a..82584155de5e 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-RSpec.describe 'layouts/nav/sidebar/_project' do
+RSpec.describe 'layouts/nav/sidebar/_project', feature_category: :navigation do
   let_it_be_with_reload(:project) { create(:project, :repository) }
 
   let(:user) { project.first_owner }
@@ -67,19 +67,6 @@
     end
   end
 
-  describe 'Learn GitLab' do
-    it 'has a link to the learn GitLab' do
-      allow(view).to receive(:learn_gitlab_enabled?).and_return(true)
-      allow_next_instance_of(Onboarding::Completion) do |onboarding|
-        expect(onboarding).to receive(:percentage).and_return(20)
-      end
-
-      render
-
-      expect(rendered).to have_link('Learn GitLab', href: project_learn_gitlab_path(project))
-    end
-  end
-
   describe 'Repository' do
     it 'has a link to the project tree path' do
       render
-- 
GitLab