From 29e13db9212bdfcad9748229f9f02ad215da0c7c Mon Sep 17 00:00:00 2001
From: Robert Hunt <rhunt@gitlab.com>
Date: Wed, 11 Jan 2023 12:10:49 +0000
Subject: [PATCH] Add creating instance to onboarding view

- Added creating instance content
- Added temporary data attribute to test the content works
- Updated specs
- Updated translations
---
 .../get_project_jitsu_key.query.graphql       |   6 +
 ...tialize_product_analytics.mutation.graphql |   9 ++
 .../javascripts/product_analytics/index.js    |  10 ++
 .../components/onboarding_empty_state.vue     |  59 ++++++++
 .../product_analytics/onboarding/constants.js |  21 +++
 .../onboarding/onboarding_view.vue            | 107 +++++++++----
 .../product_analytics/dashboards.haml         |   1 +
 .../product_analytics/dashboards_spec.rb      |  35 ++++-
 .../frontend/product_analytics/mock_data.js   |  20 +++
 .../components/onboarding_empty_state_spec.js |  79 ++++++++++
 .../onboarding/onboarding_view_spec.js        | 142 ++++++++++++++++--
 .../product_analytics_app_spec.js             |   3 +-
 locale/gitlab.pot                             |   8 +-
 13 files changed, 456 insertions(+), 44 deletions(-)
 create mode 100644 ee/app/assets/javascripts/product_analytics/graphql/mutations/get_project_jitsu_key.query.graphql
 create mode 100644 ee/app/assets/javascripts/product_analytics/graphql/mutations/initialize_product_analytics.mutation.graphql
 create mode 100644 ee/app/assets/javascripts/product_analytics/onboarding/components/onboarding_empty_state.vue
 create mode 100644 ee/spec/frontend/product_analytics/mock_data.js
 create mode 100644 ee/spec/frontend/product_analytics/onboarding/components/onboarding_empty_state_spec.js

diff --git a/ee/app/assets/javascripts/product_analytics/graphql/mutations/get_project_jitsu_key.query.graphql b/ee/app/assets/javascripts/product_analytics/graphql/mutations/get_project_jitsu_key.query.graphql
new file mode 100644
index 000000000000..ca21c54eb0bb
--- /dev/null
+++ b/ee/app/assets/javascripts/product_analytics/graphql/mutations/get_project_jitsu_key.query.graphql
@@ -0,0 +1,6 @@
+query getProjectJitsuKey($projectPath: ID!) {
+  project(fullPath: $projectPath) {
+    id
+    jitsuKey
+  }
+}
diff --git a/ee/app/assets/javascripts/product_analytics/graphql/mutations/initialize_product_analytics.mutation.graphql b/ee/app/assets/javascripts/product_analytics/graphql/mutations/initialize_product_analytics.mutation.graphql
new file mode 100644
index 000000000000..5c463724fb60
--- /dev/null
+++ b/ee/app/assets/javascripts/product_analytics/graphql/mutations/initialize_product_analytics.mutation.graphql
@@ -0,0 +1,9 @@
+mutation initializeProductAnalytics($projectPath: ID!) {
+  projectInitializeProductAnalytics(input: { projectPath: $projectPath }) {
+    project {
+      id
+      fullPath
+    }
+    errors
+  }
+}
diff --git a/ee/app/assets/javascripts/product_analytics/index.js b/ee/app/assets/javascripts/product_analytics/index.js
index 4552049113aa..c9539ce5523f 100644
--- a/ee/app/assets/javascripts/product_analytics/index.js
+++ b/ee/app/assets/javascripts/product_analytics/index.js
@@ -1,4 +1,6 @@
 import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
 import AnalyticsApp from './product_analytics_app.vue';
 import createRouter from './router';
 
@@ -12,16 +14,24 @@ export default () => {
   const {
     jitsuKey,
     projectId,
+    projectFullPath,
     jitsuHost,
     jitsuProjectId,
     chartEmptyStateIllustrationPath,
   } = el.dataset;
+  Vue.use(VueApollo);
+
+  const apolloProvider = new VueApollo({
+    defaultClient: createDefaultClient(),
+  });
 
   return new Vue({
     el,
+    apolloProvider,
     router: createRouter(),
     provide: {
       jitsuKey,
+      projectFullPath,
       projectId,
       jitsuHost,
       jitsuProjectId,
diff --git a/ee/app/assets/javascripts/product_analytics/onboarding/components/onboarding_empty_state.vue b/ee/app/assets/javascripts/product_analytics/onboarding/components/onboarding_empty_state.vue
new file mode 100644
index 000000000000..df1623269b34
--- /dev/null
+++ b/ee/app/assets/javascripts/product_analytics/onboarding/components/onboarding_empty_state.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlButton, GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { EMPTY_STATE_I18N } from '../constants';
+
+export default {
+  name: 'AnalyticsEmptyState',
+  components: {
+    GlButton,
+    GlEmptyState,
+    GlLoadingIcon,
+  },
+  inject: {
+    chartEmptyStateIllustrationPath: {
+      type: String,
+    },
+  },
+  props: {
+    loading: {
+      type: Boolean,
+      required: false,
+      default: false,
+    },
+  },
+  computed: {
+    title() {
+      return this.loading ? EMPTY_STATE_I18N.loading.title : EMPTY_STATE_I18N.empty.title;
+    },
+    description() {
+      return this.loading
+        ? EMPTY_STATE_I18N.loading.description
+        : EMPTY_STATE_I18N.empty.description;
+    },
+  },
+  i18n: EMPTY_STATE_I18N,
+  docsPath: helpPagePath('user/product_analytics/index'),
+};
+</script>
+
+<template>
+  <gl-empty-state :title="title" :svg-path="chartEmptyStateIllustrationPath">
+    <template #description>
+      <p class="gl-max-w-80">
+        {{ description }}
+      </p>
+    </template>
+    <template #actions>
+      <template v-if="!loading">
+        <gl-button variant="confirm" data-testid="setup-btn" @click="$emit('initialize')">
+          {{ $options.i18n.empty.setUpBtnText }}
+        </gl-button>
+        <gl-button :href="$options.docsPath" data-testid="learn-more-btn">
+          {{ $options.i18n.empty.learnMoreBtnText }}
+        </gl-button>
+      </template>
+      <gl-loading-icon v-else size="lg" class="gl-mt-5" />
+    </template>
+  </gl-empty-state>
+</template>
diff --git a/ee/app/assets/javascripts/product_analytics/onboarding/constants.js b/ee/app/assets/javascripts/product_analytics/onboarding/constants.js
index d8526ffb4dd7..45ef74d45b0e 100644
--- a/ee/app/assets/javascripts/product_analytics/onboarding/constants.js
+++ b/ee/app/assets/javascripts/product_analytics/onboarding/constants.js
@@ -1,3 +1,5 @@
+import { s__, __ } from '~/locale';
+
 export const INSTALL_NPM_PACKAGE = `yarn add @gitlab/application-sdk-js
 
 --
@@ -17,3 +19,22 @@ export const HTML_SCRIPT_SETUP = `<script src="https://unpkg.com/@gitlab/applica
     applicationId: '$applicationId',
     host: '$host',
 });</script>`;
+
+export const JITSU_KEY_CHECK_DELAY = 1000;
+
+export const EMPTY_STATE_I18N = {
+  empty: {
+    title: s__('Product Analytics|Analyze your product with Product Analytics'),
+    description: s__(
+      'Product Analytics|Set up Product Analytics to track how your product is performing. Combine it with your GitLab data to better understand where you can improve your product and development processes.',
+    ),
+    setUpBtnText: s__('Product Analytics|Set up product analytics'),
+    learnMoreBtnText: __('Learn more'),
+  },
+  loading: {
+    title: s__('Product Analytics|Creating your product analytics instance...'),
+    description: s__(
+      'Product Analytics|This might take a while, feel free to navigate away from this page and come back later.',
+    ),
+  },
+};
diff --git a/ee/app/assets/javascripts/product_analytics/onboarding/onboarding_view.vue b/ee/app/assets/javascripts/product_analytics/onboarding/onboarding_view.vue
index 16d5df2fcc4e..c25396b02465 100644
--- a/ee/app/assets/javascripts/product_analytics/onboarding/onboarding_view.vue
+++ b/ee/app/assets/javascripts/product_analytics/onboarding/onboarding_view.vue
@@ -1,43 +1,96 @@
 <script>
-import { GlEmptyState } from '@gitlab/ui';
-import { s__, __ } from '~/locale';
-import { helpPagePath } from '~/helpers/help_page_helper';
+import { createAlert } from '~/flash';
+import initializeProductAnalyticsMutation from '../graphql/mutations/initialize_product_analytics.mutation.graphql';
+import getProjectJitsuKeyQuery from '../graphql/mutations/get_project_jitsu_key.query.graphql';
+import OnboardingEmptyState from './components/onboarding_empty_state.vue';
+import { JITSU_KEY_CHECK_DELAY } from './constants';
 
 export default {
   name: 'ProductAnalyticsOnboardingView',
   components: {
-    GlEmptyState,
+    OnboardingEmptyState,
   },
   inject: {
-    chartEmptyStateIllustrationPath: {
+    projectFullPath: {
       type: String,
     },
   },
-  i18n: {
-    title: s__('Product Analytics|Analyze your product with Product Analytics'),
-    description: s__(
-      'Product Analytics|Set up Product Analytics to track how your product is performing. Combine it with your GitLab data to better understand where you can improve your product and development processes.',
-    ),
-    setUpBtnText: s__('Product Analytics|Set up product analytics...'),
-    learnMoreBtnText: __('Learn more'),
+  data() {
+    return {
+      creatingInstance: false,
+      jitsuKey: null,
+      pollJitsuKey: false,
+    };
+  },
+  apollo: {
+    jitsuKey: {
+      query: getProjectJitsuKeyQuery,
+      variables() {
+        return {
+          projectPath: this.projectFullPath,
+        };
+      },
+      pollInterval: JITSU_KEY_CHECK_DELAY,
+      update({ project }) {
+        const { jitsuKey } = project || {};
+
+        this.pollJitsuKey = !jitsuKey;
+
+        return jitsuKey;
+      },
+      skip() {
+        return !this.pollJitsuKey;
+      },
+      error(err) {
+        this.showError(err);
+        this.pollJitsuKey = false;
+      },
+    },
+  },
+  computed: {
+    loading() {
+      return this.creatingInstance || this.pollJitsuKey;
+    },
+  },
+  methods: {
+    showError(error, captureError = true) {
+      createAlert({
+        message: error.message,
+        captureError,
+        error,
+      });
+    },
+    async initializeProductAnalytics() {
+      this.creatingInstance = true;
+      try {
+        const { data } = await this.$apollo.mutate({
+          mutation: initializeProductAnalyticsMutation,
+          variables: {
+            projectPath: this.projectFullPath,
+          },
+          context: {
+            isSingleRequest: true,
+          },
+        });
+
+        const [error] = data?.projectInitializeProductAnalytics?.errors || [];
+
+        if (error) {
+          this.showError(new Error(error), false);
+        } else {
+          this.pollJitsuKey = true;
+        }
+      } catch (err) {
+        // TODO: Update to show the tracking codes view when no error in https://gitlab.com/gitlab-org/gitlab/-/issues/381320
+        this.showError(err);
+      } finally {
+        this.creatingInstance = false;
+      }
+    },
   },
-  docsPath: helpPagePath('user/product_analytics/index'),
 };
 </script>
 
 <template>
-  <gl-empty-state
-    :title="$options.i18n.title"
-    :svg-path="chartEmptyStateIllustrationPath"
-    :primary-button-text="$options.i18n.setUpBtnText"
-    primary-button-link="#"
-    :secondary-button-text="$options.i18n.learnMoreBtnText"
-    :secondary-button-link="$options.docsPath"
-  >
-    <template #description>
-      <p class="gl-max-w-80">
-        {{ $options.i18n.description }}
-      </p>
-    </template>
-  </gl-empty-state>
+  <onboarding-empty-state :loading="loading" @initialize="initializeProductAnalytics" />
 </template>
diff --git a/ee/app/views/projects/product_analytics/dashboards.haml b/ee/app/views/projects/product_analytics/dashboards.haml
index 16875fbf180a..405d2cf40862 100644
--- a/ee/app/views/projects/product_analytics/dashboards.haml
+++ b/ee/app/views/projects/product_analytics/dashboards.haml
@@ -7,6 +7,7 @@
     jitsu_host: Gitlab::CurrentSettings.current_application_settings.jitsu_host,
     jitsu_project_id: Gitlab::CurrentSettings.current_application_settings.jitsu_project_xid,
     chart_empty_state_illustration_path: image_path('illustrations/chart-empty-state.svg'),
+    project_full_path: @project.full_path,
   }
 }
   = gl_loading_icon(size: 'lg', css_class: 'gl-my-7')
diff --git a/ee/spec/features/projects/product_analytics/dashboards_spec.rb b/ee/spec/features/projects/product_analytics/dashboards_spec.rb
index ef2e1f94a4a3..8b954b90eeb8 100644
--- a/ee/spec/features/projects/product_analytics/dashboards_spec.rb
+++ b/ee/spec/features/projects/product_analytics/dashboards_spec.rb
@@ -30,7 +30,7 @@
     end
   end
 
-  shared_examples 'renders the onboarding view' do
+  shared_examples 'renders the onboarding empty state' do
     before do
       visit_page
     end
@@ -93,7 +93,38 @@
         end
 
         context 'without the Jitsu key' do
-          it_behaves_like 'renders the onboarding view'
+          it_behaves_like 'renders the onboarding empty state'
+
+          context 'when setting up a new instance' do
+            before do
+              visit_page
+              click_button s_('Product Analytics|Set up product analytics')
+            end
+
+            it 'renders the creating instance loading screen' do
+              expect(page).to have_content(s_('Product Analytics|Creating your product analytics instance...'))
+            end
+
+            # TODO: Update to show the tracking codes view when no error in https://gitlab.com/gitlab-org/gitlab/-/issues/381320
+            it 'returns back to the onboarding empty state after creating the new instance' do
+              wait_for_requests
+
+              expect(page).to have_content(s_('Product Analytics|Analyze your product with Product Analytics'))
+            end
+
+            context 'when a new instance has already been initialized' do
+              before do
+                visit_page
+              end
+
+              it 'renders an error alert when setting up a new instance' do
+                click_button s_('Product Analytics|Set up product analytics')
+
+                expect(find('[data-testid="alert-danger"]'))
+                  .to have_text(/Product analytics initialization is already (completed|in progress)/)
+              end
+            end
+          end
         end
 
         context 'with the Jitsu key' do
diff --git a/ee/spec/frontend/product_analytics/mock_data.js b/ee/spec/frontend/product_analytics/mock_data.js
new file mode 100644
index 000000000000..faca2fdfb310
--- /dev/null
+++ b/ee/spec/frontend/product_analytics/mock_data.js
@@ -0,0 +1,20 @@
+export const createInstanceResponse = (errors = []) => ({
+  data: {
+    projectInitializeProductAnalytics: {
+      project: {
+        id: 'gid://gitlab/Project/2',
+        fullPath: '',
+      },
+      errors,
+    },
+  },
+});
+
+export const getJitsuKeyResponse = (jitsuKey = null) => ({
+  data: {
+    project: {
+      id: 'gid://gitlab/Project/2',
+      jitsuKey,
+    },
+  },
+});
diff --git a/ee/spec/frontend/product_analytics/onboarding/components/onboarding_empty_state_spec.js b/ee/spec/frontend/product_analytics/onboarding/components/onboarding_empty_state_spec.js
new file mode 100644
index 000000000000..350b61f48ba3
--- /dev/null
+++ b/ee/spec/frontend/product_analytics/onboarding/components/onboarding_empty_state_spec.js
@@ -0,0 +1,79 @@
+import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+import OnboardingEmptyState from 'ee/product_analytics/onboarding/components/onboarding_empty_state.vue';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import { EMPTY_STATE_I18N } from 'ee/product_analytics/onboarding/constants';
+
+describe('OnboardingEmptyState', () => {
+  let wrapper;
+
+  const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+  const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
+  const findSetupBtn = () => wrapper.findByTestId('setup-btn');
+  const findLearnMoreBtn = () => wrapper.findByTestId('learn-more-btn');
+
+  const createWrapper = (props = {}) => {
+    wrapper = shallowMountExtended(OnboardingEmptyState, {
+      provide: {
+        chartEmptyStateIllustrationPath: TEST_HOST,
+      },
+      propsData: {
+        ...props,
+      },
+    });
+  };
+
+  describe('default behaviour', () => {
+    beforeEach(() => {
+      createWrapper();
+    });
+
+    it('should render the empty state with expected props', () => {
+      const emptyState = findEmptyState();
+
+      expect(emptyState.props()).toMatchObject({
+        title: EMPTY_STATE_I18N.empty.title,
+        svgPath: TEST_HOST,
+      });
+      expect(emptyState.text()).toContain(EMPTY_STATE_I18N.empty.description);
+      expect(findSetupBtn().text()).toBe(EMPTY_STATE_I18N.empty.setUpBtnText);
+      expect(findLearnMoreBtn().text()).toBe(EMPTY_STATE_I18N.empty.learnMoreBtnText);
+      expect(findLearnMoreBtn().attributes('href')).toBe('/help/user/product_analytics/index');
+    });
+
+    it('should emit `initialize` when the setup button is clicked', () => {
+      findSetupBtn().vm.$emit('click');
+
+      expect(wrapper.emitted('initialize')).toStrictEqual([[]]);
+    });
+
+    it('does not render the loading icon', () => {
+      expect(findLoadingIcon().exists()).toBe(false);
+    });
+  });
+
+  describe('when loading', () => {
+    beforeEach(() => {
+      createWrapper({ loading: true });
+    });
+
+    it('should render the loading state', () => {
+      const emptyState = findEmptyState();
+
+      expect(emptyState.props()).toMatchObject({
+        title: EMPTY_STATE_I18N.loading.title,
+        svgPath: TEST_HOST,
+      });
+      expect(emptyState.text()).toContain(EMPTY_STATE_I18N.loading.description);
+    });
+
+    it('renders the loading icon', () => {
+      expect(findLoadingIcon().exists()).toBe(true);
+    });
+
+    it('does not render the buttons', () => {
+      expect(findSetupBtn().exists()).toBe(false);
+      expect(findLearnMoreBtn().exists()).toBe(false);
+    });
+  });
+});
diff --git a/ee/spec/frontend/product_analytics/onboarding/onboarding_view_spec.js b/ee/spec/frontend/product_analytics/onboarding/onboarding_view_spec.js
index 5f6cf79368c1..af39e42e1fad 100644
--- a/ee/spec/frontend/product_analytics/onboarding/onboarding_view_spec.js
+++ b/ee/spec/frontend/product_analytics/onboarding/onboarding_view_spec.js
@@ -1,38 +1,154 @@
-import { GlEmptyState } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
 import ProductAnalyticsOnboardingView from 'ee/product_analytics/onboarding/onboarding_view.vue';
+import OnboardingEmptyState from 'ee/product_analytics/onboarding/components/onboarding_empty_state.vue';
+import initializeProductAnalyticsMutation from 'ee/product_analytics/graphql/mutations/initialize_product_analytics.mutation.graphql';
+import getProjectJitsuKeyQuery from 'ee/product_analytics/graphql/mutations/get_project_jitsu_key.query.graphql';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { TEST_HOST } from 'spec/test_constants';
+import waitForPromises from 'helpers/wait_for_promises';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { createAlert } from '~/flash';
+import { JITSU_KEY_CHECK_DELAY } from 'ee/product_analytics/onboarding/constants';
+import { createInstanceResponse, getJitsuKeyResponse } from '../mock_data';
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
 
 describe('ProductAnalyticsOnboardingView', () => {
   let wrapper;
 
-  const findEmptyState = () => wrapper.findComponent(GlEmptyState);
+  const fatalError = new Error('GraphQL networkError');
+  const apiErrorMsg = 'Product analytics initialization is already complete';
+  const jitsuKey = 'valid-jitsu-key';
+  const mockCreateInstanceSuccess = jest.fn().mockResolvedValue(createInstanceResponse());
+  const mockCreateInstanceLoading = jest.fn().mockResolvedValue(new Promise(() => {}));
+  const mockCreateInstanceApiError = jest
+    .fn()
+    .mockResolvedValue(createInstanceResponse([apiErrorMsg]));
+  const mockCreateInstanceFatalError = jest.fn().mockRejectedValue(fatalError);
+  const mockGetJitsuKeyHasKeySuccess = jest.fn().mockResolvedValue(getJitsuKeyResponse(jitsuKey));
+  const mockGetJitsuKeyHasKeySuccessRetry = jest
+    .fn()
+    .mockResolvedValueOnce(getJitsuKeyResponse(null))
+    .mockResolvedValueOnce(getJitsuKeyResponse(jitsuKey));
+  const mockGetJitsuKeyError = jest.fn().mockRejectedValue(fatalError);
+
+  const findEmptyState = () => wrapper.findComponent(OnboardingEmptyState);
 
-  const createWrapper = () => {
+  const createWrapper = (handlers) => {
     wrapper = shallowMountExtended(ProductAnalyticsOnboardingView, {
+      apolloProvider: createMockApollo(handlers),
       provide: {
         chartEmptyStateIllustrationPath: TEST_HOST,
+        projectFullPath: 'group-1/project-1',
       },
     });
   };
 
+  const waitForApolloTimers = async () => {
+    jest.advanceTimersByTime(JITSU_KEY_CHECK_DELAY);
+    return waitForPromises();
+  };
+
+  afterEach(() => {
+    createAlert.mockClear();
+  });
+
   describe('when mounted', () => {
     beforeEach(() => {
       createWrapper();
     });
 
-    it('should render the empty state with expected props', () => {
-      const emptyState = findEmptyState();
+    it('should render the empty state that is not loading', () => {
+      expect(findEmptyState().props('loading')).toBe(false);
+    });
+
+    it('has a polling interval for querying the jitsu key', () => {
+      expect(wrapper.vm.$apollo.queries.jitsuKey.options.pollInterval).toBe(JITSU_KEY_CHECK_DELAY);
+    });
+  });
+
+  describe('when creating an instance', () => {
+    it('should show loading while the instance is initializing', async () => {
+      createWrapper([[initializeProductAnalyticsMutation, mockCreateInstanceLoading]]);
+
+      await findEmptyState().vm.$emit('initialize');
+
+      expect(findEmptyState().props('loading')).toBe(true);
+    });
+
+    it('should show loading and poll for the jitsu key while it is null', async () => {
+      createWrapper([
+        [initializeProductAnalyticsMutation, mockCreateInstanceSuccess],
+        [getProjectJitsuKeyQuery, mockGetJitsuKeyHasKeySuccessRetry],
+      ]);
+
+      findEmptyState().vm.$emit('initialize');
+
+      await waitForPromises();
+
+      expect(mockGetJitsuKeyHasKeySuccessRetry.mock.calls).toHaveLength(1);
+      expect(findEmptyState().props('loading')).toBe(true);
+
+      await waitForApolloTimers();
+
+      expect(mockGetJitsuKeyHasKeySuccessRetry.mock.calls).toHaveLength(2);
+      expect(findEmptyState().props('loading')).toBe(false);
+    });
+
+    it('should return the jitsu key if creating an instance is successful', async () => {
+      createWrapper([
+        [initializeProductAnalyticsMutation, mockCreateInstanceSuccess],
+        [getProjectJitsuKeyQuery, mockGetJitsuKeyHasKeySuccess],
+      ]);
+
+      findEmptyState().vm.$emit('initialize');
 
-      expect(emptyState.props()).toMatchObject({
-        title: ProductAnalyticsOnboardingView.i18n.title,
-        svgPath: TEST_HOST,
-        primaryButtonText: ProductAnalyticsOnboardingView.i18n.setUpBtnText,
-        primaryButtonLink: '#',
-        secondaryButtonText: ProductAnalyticsOnboardingView.i18n.learnMoreBtnText,
-        secondaryButtonLink: ProductAnalyticsOnboardingView.docsPath,
+      await waitForPromises();
+
+      expect(mockGetJitsuKeyHasKeySuccess).toHaveBeenCalledTimes(1);
+      expect(findEmptyState().props('loading')).toBe(false);
+    });
+
+    it('should show the error if getting the jitsu key throws an error', async () => {
+      createWrapper([
+        [initializeProductAnalyticsMutation, mockCreateInstanceSuccess],
+        [getProjectJitsuKeyQuery, mockGetJitsuKeyError],
+      ]);
+
+      findEmptyState().vm.$emit('initialize');
+
+      await waitForPromises();
+
+      expect(createAlert).toHaveBeenCalledWith({
+        message: fatalError.message,
+        captureError: true,
+        error: fatalError,
       });
-      expect(emptyState.text()).toContain(ProductAnalyticsOnboardingView.i18n.description);
+    });
+
+    describe('when a create instance error occurs', () => {
+      it.each`
+        type          | mockError                       | alertError                | captureError
+        ${'instance'} | ${mockCreateInstanceFatalError} | ${fatalError}             | ${true}
+        ${'api'}      | ${mockCreateInstanceApiError}   | ${new Error(apiErrorMsg)} | ${false}
+      `(
+        'should create an alert for $type errors',
+        async ({ mockError, alertError, captureError }) => {
+          createWrapper([[initializeProductAnalyticsMutation, mockError]]);
+
+          findEmptyState().vm.$emit('initialize');
+          await waitForPromises();
+
+          expect(createAlert).toHaveBeenCalledWith({
+            message: alertError.message,
+            captureError,
+            error: alertError,
+          });
+        },
+      );
     });
   });
 });
diff --git a/ee/spec/frontend/product_analytics/product_analytics_app_spec.js b/ee/spec/frontend/product_analytics/product_analytics_app_spec.js
index 89cd8b18b5c8..cc7315bcf216 100644
--- a/ee/spec/frontend/product_analytics/product_analytics_app_spec.js
+++ b/ee/spec/frontend/product_analytics/product_analytics_app_spec.js
@@ -28,11 +28,12 @@ describe('ProductAnalyticsApp', () => {
     wrapper = shallowMount(AnalyticsApp, {
       router: createRouter(),
       provide: {
+        chartEmptyStateIllustrationPath: TEST_HOST,
         jitsuKey: '123',
         projectId: '1',
         jitsuHost: TEST_HOST,
         jitsuProjectId: '',
-        chartEmptyStateIllustrationPath: TEST_HOST,
+        projectFullPath: 'group-1/project-1',
         ...provided,
       },
     });
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index ca17d528fc37..14c025ef3920 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -31810,6 +31810,9 @@ msgstr ""
 msgid "Product Analytics|Analyze your product with Product Analytics"
 msgstr ""
 
+msgid "Product Analytics|Creating your product analytics instance..."
+msgstr ""
+
 msgid "Product Analytics|For the product analytics dashboard to start showing you some data, you need to add the analytics tracking code to your project."
 msgstr ""
 
@@ -31831,7 +31834,7 @@ msgstr ""
 msgid "Product Analytics|Set up Product Analytics to track how your product is performing. Combine it with your GitLab data to better understand where you can improve your product and development processes."
 msgstr ""
 
-msgid "Product Analytics|Set up product analytics..."
+msgid "Product Analytics|Set up product analytics"
 msgstr ""
 
 msgid "Product Analytics|Steps to add product analytics as a CommonJS module"
@@ -31846,6 +31849,9 @@ msgstr ""
 msgid "Product Analytics|The host to send all tracking events to"
 msgstr ""
 
+msgid "Product Analytics|This might take a while, feel free to navigate away from this page and come back later."
+msgstr ""
+
 msgid "Product Analytics|To instrument your application, select one of the options below. After an option has been instrumented and data is being collected, this page will progress to the next step."
 msgstr ""
 
-- 
GitLab