diff --git a/ee/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue b/ee/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
index 5fd004c9209732db039490d8191e4113c9714043..30e383c17a74e60e8561dea7eb0ac6ea18b1f381 100644
--- a/ee/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
+++ b/ee/app/assets/javascripts/usage_quotas/storage/components/namespace_storage_app.vue
@@ -1,5 +1,6 @@
 <script>
 import {
+  GlAlert,
   GlLink,
   GlSprintf,
   GlModalDirective,
@@ -7,7 +8,9 @@ import {
   GlIcon,
   GlKeysetPagination,
 } from '@gitlab/ui';
-import * as Sentry from '@sentry/browser';
+import { isEmpty } from 'lodash';
+import { s__ } from '~/locale';
+import { captureException } from '~/runner/sentry_utils';
 import { parseBoolean } from '~/lib/utils/common_utils';
 import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
 import { PROJECT_TABLE_LABEL_STORAGE_USAGE } from '../constants';
@@ -26,6 +29,7 @@ import ContainerRegistryUsage from './container_registry_usage.vue';
 export default {
   name: 'NamespaceStorageApp',
   components: {
+    GlAlert,
     GlLink,
     GlIcon,
     GlButton,
@@ -66,6 +70,10 @@ export default {
       result() {
         this.firstFetch = false;
       },
+      error(error) {
+        this.loadingError = true;
+        captureException({ error, component: this.$options.name });
+      },
     },
     dependencyProxyTotalSize: {
       query: GetDependencyProxyTotalSizeQuery,
@@ -78,10 +86,7 @@ export default {
         return group?.dependencyProxyTotalSize;
       },
       error(error) {
-        Sentry.withScope((scope) => {
-          scope.setTag('component', this.$options.name);
-          Sentry.captureException(error);
-        });
+        captureException({ error, component: this.$options.name });
       },
     },
   },
@@ -104,6 +109,7 @@ export default {
   },
   i18n: {
     PROJECT_TABLE_LABEL_STORAGE_USAGE,
+    errorMessageText: s__('UsageQuota|Something went wrong while loading usage details'),
   },
   data() {
     return {
@@ -111,6 +117,7 @@ export default {
       searchTerm: '',
       firstFetch: true,
       dependencyProxyTotalSize: '',
+      loadingError: false,
     };
   },
   computed: {
@@ -148,6 +155,10 @@ export default {
       return this.namespace.projects?.pageInfo ?? {};
     },
     shouldShowStorageInlineAlert() {
+      if (isEmpty(this.namespace)) {
+        return false;
+      }
+
       if (this.firstFetch) {
         // for initial load check if the data fetch is done (isQueryLoading)
         return this.isAdditionalStorageFlagEnabled && !this.isQueryLoading;
@@ -160,6 +171,9 @@ export default {
     showPagination() {
       return Boolean(this.pageInfo?.hasPreviousPage || this.pageInfo?.hasNextPage);
     },
+    isStorageUsageStatisticsLoading() {
+      return this.loadingError || this.isQueryLoading;
+    },
   },
   methods: {
     handleSearch(input) {
@@ -196,6 +210,9 @@ export default {
 </script>
 <template>
   <div>
+    <gl-alert v-if="loadingError" variant="danger" :dismissible="false" class="gl-mt-4">
+      {{ $options.i18n.errorMessageText }}
+    </gl-alert>
     <storage-inline-alert
       v-if="shouldShowStorageInlineAlert"
       :contains-locked-projects="namespace.containsLockedProjects"
@@ -213,6 +230,7 @@ export default {
         :actual-repository-size-limit="storageStatistics.actualRepositorySizeLimit"
         :total-repository-size="storageStatistics.totalRepositorySize"
         :total-repository-size-excess="storageStatistics.totalRepositorySizeExcess"
+        :loading="isStorageUsageStatisticsLoading"
       />
       <usage-statistics v-else :root-storage-statistics="storageStatistics" />
     </div>
diff --git a/ee/app/assets/javascripts/usage_quotas/storage/components/storage_statistics_card.vue b/ee/app/assets/javascripts/usage_quotas/storage/components/storage_statistics_card.vue
index 52e6c5258884b27d0d33523d8456a72084bc73fd..a9d7d0648bfa3e64b298a086b016711036831175 100644
--- a/ee/app/assets/javascripts/usage_quotas/storage/components/storage_statistics_card.vue
+++ b/ee/app/assets/javascripts/usage_quotas/storage/components/storage_statistics_card.vue
@@ -1,10 +1,10 @@
 <script>
-import { GlProgressBar } from '@gitlab/ui';
+import { GlProgressBar, GlSkeletonLoader } from '@gitlab/ui';
 import { formatSizeAndSplit } from 'ee/usage_quotas/storage/utils';
 
 export default {
   name: 'StorageStatisticsCard',
-  components: { GlProgressBar },
+  components: { GlProgressBar, GlSkeletonLoader },
   props: {
     totalStorage: {
       type: Number,
@@ -21,6 +21,10 @@ export default {
       required: false,
       default: false,
     },
+    loading: {
+      type: Boolean,
+      required: true,
+    },
   },
   computed: {
     formattedUsage() {
@@ -71,24 +75,32 @@ export default {
     class="gl-bg-white gl-border-1 gl-border-gray-100 gl-border-solid gl-p-5 gl-rounded-base"
     data-testid="container"
   >
-    <div class="gl-display-flex gl-justify-content-space-between">
-      <p class="gl-font-size-h-display gl-font-weight-bold gl-mb-3" data-testid="denominator">
-        {{ usageValue }}
-        <span class="gl-font-lg">{{ usageUnit }}</span>
-        <span v-if="shouldRenderTotalBlock" data-testid="denominator-total">
-          /
-          {{ totalValue }}
-          <span class="gl-font-lg">{{ totalUnit }}</span>
-        </span>
-      </p>
+    <gl-skeleton-loader v-if="loading" :height="64">
+      <rect width="140" height="30" x="5" y="0" rx="4" />
+      <rect width="240" height="10" x="5" y="40" rx="4" />
+      <rect width="340" height="10" x="5" y="54" rx="4" />
+    </gl-skeleton-loader>
 
-      <div data-testid="actions">
-        <slot name="actions"></slot>
+    <div v-else>
+      <div class="gl-display-flex gl-justify-content-space-between">
+        <p class="gl-font-size-h-display gl-font-weight-bold gl-mb-3" data-testid="denominator">
+          {{ usageValue }}
+          <span class="gl-font-lg">{{ usageUnit }}</span>
+          <span v-if="shouldRenderTotalBlock" data-testid="denominator-total">
+            /
+            {{ totalValue }}
+            <span class="gl-font-lg">{{ totalUnit }}</span>
+          </span>
+        </p>
+
+        <div data-testid="actions">
+          <slot name="actions"></slot>
+        </div>
       </div>
+      <p class="gl-font-weight-bold" data-testid="description">
+        <slot name="description"></slot>
+      </p>
+      <gl-progress-bar v-if="shouldShowProgressBar" :value="percentage" />
     </div>
-    <p class="gl-font-weight-bold" data-testid="description">
-      <slot name="description"></slot>
-    </p>
-    <gl-progress-bar v-if="shouldShowProgressBar" :value="percentage" />
   </div>
 </template>
diff --git a/ee/app/assets/javascripts/usage_quotas/storage/components/storage_usage_statistics.vue b/ee/app/assets/javascripts/usage_quotas/storage/components/storage_usage_statistics.vue
index cd18fa5215022ba9bf3e681ecc51954a4f476c94..74aa4f3f366e89de17e4b45c4c0c68c242b3f6eb 100644
--- a/ee/app/assets/javascripts/usage_quotas/storage/components/storage_usage_statistics.vue
+++ b/ee/app/assets/javascripts/usage_quotas/storage/components/storage_usage_statistics.vue
@@ -44,6 +44,10 @@ export default {
       required: false,
       default: false,
     },
+    loading: {
+      type: Boolean,
+      required: true,
+    },
   },
   i18n: {
     purchasedUsageHelpLink: projectHelpPaths.usageQuotas,
@@ -91,6 +95,7 @@ export default {
       :used-storage="usedStorageAmount"
       :total-storage="storageLimitEnforced ? repositorySizeLimit : null"
       :show-progress-bar="storageLimitEnforced"
+      :loading="loading"
       data-testid="namespace-usage-card"
       class="gl-w-half gl-md-mr-5"
     >
@@ -113,6 +118,7 @@ export default {
       :used-storage="purchasedUsedStorage"
       :total-storage="purchasedTotalStorage"
       :show-progress-bar="storageLimitEnforced"
+      :loading="loading"
       data-testid="purchased-usage-card"
       class="gl-w-half"
     >
diff --git a/ee/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js b/ee/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js
index 9182908713b23ba2322fde2f3903a2a8270df28a..3976da245d0440b6a36e075b048a583e3ae07505 100644
--- a/ee/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js
+++ b/ee/spec/frontend/usage_quotas/storage/components/namespace_storage_app_spec.js
@@ -1,64 +1,96 @@
-import { mount } from '@vue/test-utils';
+import { GlAlert } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import Vue from 'vue';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import { captureException } from '~/runner/sentry_utils';
 import NamespaceStorageApp from 'ee/usage_quotas/storage/components/namespace_storage_app.vue';
 import CollapsibleProjectStorageDetail from 'ee/usage_quotas/storage/components/collapsible_project_storage_detail.vue';
-import ProjectList from 'ee/usage_quotas/storage/components/project_list.vue';
-import StorageInlineAlert from 'ee/usage_quotas/storage/components/storage_inline_alert.vue';
-import TemporaryStorageIncreaseModal from 'ee/usage_quotas/storage/components/temporary_storage_increase_modal.vue';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import getNamespaceStorageQuery from 'ee/usage_quotas/storage/queries/namespace_storage.query.graphql';
+import getDependencyProxyTotalSizeQuery from 'ee/usage_quotas/storage/queries/dependency_proxy_usage.query.graphql';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { formatUsageSize } from 'ee/usage_quotas/storage/utils';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import UsageGraph from 'ee/usage_quotas/storage/components/usage_graph.vue';
 import UsageStatistics from 'ee/usage_quotas/storage/components/usage_statistics.vue';
 import StorageUsageStatistics from 'ee/usage_quotas/storage/components/storage_usage_statistics.vue';
-import UsageGraph from 'ee/usage_quotas/storage/components/usage_graph.vue';
+import StorageInlineAlert from 'ee/usage_quotas/storage/components/storage_inline_alert.vue';
 import DependencyProxyUsage from 'ee/usage_quotas/storage/components/dependency_proxy_usage.vue';
 import ContainerRegistryUsage from 'ee/usage_quotas/storage/components/container_registry_usage.vue';
-import { formatUsageSize } from 'ee/usage_quotas/storage/utils';
-import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
+import ProjectList from 'ee/usage_quotas/storage/components/project_list.vue';
+import TemporaryStorageIncreaseModal from 'ee/usage_quotas/storage/components/temporary_storage_increase_modal.vue';
 import {
-  namespaceData,
-  withRootStorageStatistics,
   defaultNamespaceProvideValues,
+  mockedNamespaceStorageResponse,
+  mockDependencyProxyResponse,
 } from '../mock_data';
 
 const TEST_LIMIT = 1000;
 
+jest.mock('~/flash');
+jest.mock('~/runner/sentry_utils');
+
+Vue.use(VueApollo);
+
 describe('NamespaceStorageApp', () => {
   let wrapper;
-  let $apollo;
 
-  const findTotalUsage = () => wrapper.find("[data-testid='total-usage']");
-  const findPurchaseStorageLink = () => wrapper.find("[data-testid='purchase-storage-link']");
-  const findTemporaryStorageIncreaseButton = () =>
-    wrapper.find("[data-testid='temporary-storage-increase-button']");
+  function createMockApolloProvider(response = mockedNamespaceStorageResponse) {
+    const successHandler = jest.fn().mockResolvedValue(response);
+    const requestHandlers = [
+      [getNamespaceStorageQuery, successHandler],
+      [getDependencyProxyTotalSizeQuery, jest.fn().mockResolvedValue(mockDependencyProxyResponse)],
+    ];
+
+    return createMockApollo(requestHandlers);
+  }
+
+  function createPendingMockApolloProvider() {
+    const successHandler = new Promise(() => {});
+    const requestHandlers = [
+      [getNamespaceStorageQuery, successHandler],
+      [getDependencyProxyTotalSizeQuery, jest.fn().mockResolvedValue(mockDependencyProxyResponse)],
+    ];
+
+    return createMockApollo(requestHandlers);
+  }
+
+  function createFailedMockApolloProvider() {
+    const failedHandler = jest.fn().mockRejectedValue(new Error('Network error!'));
+    const requestHandlers = [
+      [getNamespaceStorageQuery, failedHandler],
+      [getDependencyProxyTotalSizeQuery, jest.fn().mockResolvedValue(mockDependencyProxyResponse)],
+    ];
+
+    return createMockApollo(requestHandlers);
+  }
+
+  const findTotalUsage = () => wrapper.findByTestId('total-usage');
   const findUsageGraph = () => wrapper.findComponent(UsageGraph);
   const findUsageStatistics = () => wrapper.findComponent(UsageStatistics);
-  const findStorageUsageStatistics = () => wrapper.findComponent(StorageUsageStatistics);
   const findStorageInlineAlert = () => wrapper.findComponent(StorageInlineAlert);
+  const findPurchaseStorageLink = () => wrapper.find("[data-testid='purchase-storage-link']");
+  const findDependencyProxy = () => wrapper.findComponent(DependencyProxyUsage);
+  const findStorageUsageStatistics = () => wrapper.findComponent(StorageUsageStatistics);
+  const findTemporaryStorageIncreaseButton = () =>
+    wrapper.find("[data-testid='temporary-storage-increase-button']");
   const findProjectList = () => wrapper.findComponent(ProjectList);
   const findPrevButton = () => wrapper.find('[data-testid="prevButton"]');
   const findNextButton = () => wrapper.find('[data-testid="nextButton"]');
-  const findDependencyProxy = () => wrapper.findComponent(DependencyProxyUsage);
   const findContainerRegistry = () => wrapper.findComponent(ContainerRegistryUsage);
+  const findAlert = () => wrapper.findComponent(GlAlert);
 
   const createComponent = ({
     provide = {},
     storageLimitEnforced = false,
-    loading = false,
     additionalRepoStorageByNamespace = false,
-    namespace = {},
     dependencyProxyTotalSize = '',
     isFreeNamespace = false,
+    mockApollo = {},
   } = {}) => {
-    $apollo = {
-      queries: {
-        namespace: {
-          loading,
-        },
-        dependencyProxyTotalSize: {
-          loading,
-        },
-      },
-    };
-
-    wrapper = mount(NamespaceStorageApp, {
-      mocks: { $apollo },
+    wrapper = mountExtended(NamespaceStorageApp, {
+      apolloProvider: mockApollo,
       directives: {
         GlModalDirective: createMockDirective(),
       },
@@ -73,104 +105,84 @@ describe('NamespaceStorageApp', () => {
       },
       data() {
         return {
-          namespace,
           dependencyProxyTotalSize,
         };
       },
     });
   };
 
-  beforeEach(() => {
-    createComponent({
-      namespace: namespaceData,
-    });
-  });
+  let mockApollo;
 
   afterEach(() => {
     wrapper.destroy();
   });
 
-  it('renders the 2 projects', async () => {
-    expect(wrapper.findAllComponents(CollapsibleProjectStorageDetail)).toHaveLength(3);
-  });
-
-  describe('limit', () => {
-    it('when limit is set it renders limit information', async () => {
-      expect(wrapper.text()).toContain(formatUsageSize(namespaceData.limit));
+  describe('project list', () => {
+    beforeEach(async () => {
+      mockApollo = createMockApolloProvider();
+      createComponent({ mockApollo });
+      await waitForPromises();
     });
 
-    it('when limit is 0 it does not render limit information', async () => {
-      createComponent({
-        namespace: { ...namespaceData, limit: 0 },
-      });
-
-      expect(wrapper.text()).not.toContain(formatUsageSize(0));
+    it('renders the 2 projects', () => {
+      expect(wrapper.findAllComponents(CollapsibleProjectStorageDetail)).toHaveLength(2);
     });
   });
 
-  it('renders Not applicable for totalUsage when no rootStorageStatistics is provided', async () => {
-    expect(findTotalUsage().text()).toContain('Not applicable.');
-  });
+  describe('size limit', () => {
+    it('does not render limit information when storageSizeLimit is 0', async () => {
+      const namespaceWithZeroLimit = { ...mockedNamespaceStorageResponse };
+      namespaceWithZeroLimit.data.namespace.storageSizeLimit = 0;
+      mockApollo = createMockApolloProvider(namespaceWithZeroLimit);
+      createComponent({ mockApollo });
+      await waitForPromises();
 
-  describe('with rootStorageStatistics information', () => {
-    beforeEach(() => {
-      createComponent({
-        namespace: withRootStorageStatistics,
-      });
+      expect(wrapper.text()).not.toContain(formatUsageSize(0));
     });
 
-    it('renders total usage', async () => {
-      expect(findTotalUsage().text()).toContain(withRootStorageStatistics.totalUsage);
-    });
+    it('renders limit information when storageSizeLimit is set to other numbers', async () => {
+      const namespaceWithLimit = { ...mockedNamespaceStorageResponse };
+      namespaceWithLimit.data.namespace.storageSizeLimit = TEST_LIMIT;
+      mockApollo = createMockApolloProvider(namespaceWithLimit);
+      createComponent({ mockApollo });
+      await waitForPromises();
 
-    describe('with additional_repo_storage_by_namespace feature', () => {
-      it('usage_graph component hidden is when feature is false', async () => {
-        expect(findUsageGraph().exists()).toBe(true);
-        expect(findUsageStatistics().exists()).toBe(false);
-        expect(findStorageInlineAlert().exists()).toBe(false);
-      });
-
-      it('usage_statistics component is rendered when feature is true', async () => {
-        createComponent({
-          additionalRepoStorageByNamespace: true,
-          namespace: withRootStorageStatistics,
-        });
-
-        expect(findUsageStatistics().exists()).toBe(true);
-        expect(findUsageGraph().exists()).toBe(false);
-        expect(findStorageInlineAlert().exists()).toBe(true);
-      });
+      expect(wrapper.text()).toContain(
+        formatUsageSize(namespaceWithLimit.data.namespace.storageSizeLimit),
+      );
     });
   });
 
   describe('purchase storage link', () => {
-    describe('when purchaseStorageUrl is not set', () => {
-      it('does not render an additional link', () => {
-        expect(findPurchaseStorageLink().exists()).toBe(false);
-      });
+    it('does not render an additional link when purchaseStorageUrl is not set', async () => {
+      mockApollo = createMockApolloProvider();
+      createComponent({ mockApollo });
+      await waitForPromises();
+
+      expect(findPurchaseStorageLink().exists()).toBe(false);
     });
 
-    describe('when purchaseStorageUrl is set', () => {
-      beforeEach(() => {
-        createComponent({ provide: { purchaseStorageUrl: 'customers.gitlab.com' } });
-      });
+    it('does render link when purchaseStorageUrl is set', async () => {
+      mockApollo = createMockApolloProvider();
+      createComponent({ mockApollo, provide: { purchaseStorageUrl: 'customers.gitlab.com' } });
+      await waitForPromises();
 
-      it('does render link', () => {
-        const link = findPurchaseStorageLink();
+      const link = findPurchaseStorageLink();
 
-        expect(link.exists()).toBe(true);
-        expect(link.attributes('href')).toBe('customers.gitlab.com');
-      });
+      expect(link.exists()).toBe(true);
+      expect(link.attributes('href')).toBe('customers.gitlab.com');
     });
   });
 
   describe('Dependency proxy usage', () => {
-    beforeEach(() => {
+    beforeEach(async () => {
+      mockApollo = createMockApolloProvider();
       createComponent({
+        mockApollo,
         additionalRepoStorageByNamespace: true,
-        namespace: withRootStorageStatistics,
         dependencyProxyTotalSize: '512 bytes',
       });
+      await waitForPromises();
     });
 
     it('should show the dependency proxy usage component', () => {
@@ -179,20 +191,28 @@ describe('NamespaceStorageApp', () => {
   });
 
   describe('Container registry usage', () => {
-    it('should show the container registry usage component', () => {
+    it('should show the container registry usage component', async () => {
+      mockApollo = createMockApolloProvider();
       createComponent({
-        namespace: withRootStorageStatistics,
+        mockApollo,
+        additionalRepoStorageByNamespace: true,
+        dependencyProxyTotalSize: '512 bytes',
       });
+      await waitForPromises();
 
       expect(findContainerRegistry().exists()).toBe(true);
       expect(findContainerRegistry().props()).toEqual({
         containerRegistrySize:
-          withRootStorageStatistics.rootStorageStatistics.containerRegistrySize,
+          mockedNamespaceStorageResponse.data.namespace.rootStorageStatistics.containerRegistrySize,
       });
     });
   });
 
   describe('temporary storage increase', () => {
+    const namespaceWithLimit = { ...mockedNamespaceStorageResponse };
+    namespaceWithLimit.data.namespace.storageSizeLimit = TEST_LIMIT;
+    mockApollo = createMockApolloProvider(namespaceWithLimit);
+
     describe.each`
       provide                                           | isVisible
       ${{}}                                             | ${false}
@@ -200,7 +220,7 @@ describe('NamespaceStorageApp', () => {
       ${{ isTemporaryStorageIncreaseVisible: 'true' }}  | ${true}
     `('with $provide', ({ provide, isVisible }) => {
       beforeEach(() => {
-        createComponent({ provide });
+        createComponent({ mockApollo, provide });
       });
 
       it(`renders button = ${isVisible}`, () => {
@@ -209,14 +229,14 @@ describe('NamespaceStorageApp', () => {
     });
 
     describe('when temporary storage increase is visible', () => {
-      beforeEach(() => {
+      beforeEach(async () => {
+        namespaceWithLimit.data.namespace.storageSizeLimit = TEST_LIMIT;
+        mockApollo = createMockApolloProvider(namespaceWithLimit);
         createComponent({
+          mockApollo,
           provide: { isTemporaryStorageIncreaseVisible: 'true' },
-          namespace: {
-            ...namespaceData,
-            limit: TEST_LIMIT,
-          },
         });
+        await waitForPromises();
       });
 
       it('binds button to modal', () => {
@@ -241,9 +261,10 @@ describe('NamespaceStorageApp', () => {
 
   describe('filtering projects', () => {
     beforeEach(() => {
+      mockApollo = createMockApolloProvider();
       createComponent({
+        mockApollo,
         additionalRepoStorageByNamespace: true,
-        namespace: withRootStorageStatistics,
       });
     });
 
@@ -282,22 +303,13 @@ describe('NamespaceStorageApp', () => {
   });
 
   describe('projects table pagination component', () => {
-    const namespaceWithPageInfo = (
-      pageInfo = {
-        hasPreviousPage: false,
-        hasNextPage: true,
-      },
-    ) => ({
-      namespace: {
-        ...withRootStorageStatistics,
-        projects: {
-          ...withRootStorageStatistics.projects,
-          pageInfo,
-        },
-      },
-    });
-    beforeEach(() => {
-      createComponent(namespaceWithPageInfo());
+    const namespaceWithPageInfo = { ...mockedNamespaceStorageResponse };
+    namespaceWithPageInfo.data.namespace.projects.pageInfo.hasNextPage = true;
+
+    beforeEach(async () => {
+      mockApollo = createMockApolloProvider(namespaceWithPageInfo);
+      createComponent({ mockApollo });
+      await waitForPromises();
     });
 
     it('has "Prev" button disabled', () => {
@@ -309,19 +321,22 @@ describe('NamespaceStorageApp', () => {
     });
 
     describe('apollo calls', () => {
-      beforeEach(() => {
-        createComponent(
-          namespaceWithPageInfo({
-            hasPreviousPage: true,
-            hasNextPage: true,
-          }),
-        );
-        $apollo.queries.namespace.fetchMore = jest.fn().mockResolvedValue();
+      beforeEach(async () => {
+        namespaceWithPageInfo.data.namespace.projects.pageInfo.hasPreviousPage = true;
+        namespaceWithPageInfo.data.namespace.projects.pageInfo.hasNextPage = true;
+        mockApollo = createMockApolloProvider(namespaceWithPageInfo);
+        createComponent({ mockApollo });
+
+        jest
+          .spyOn(wrapper.vm.$apollo.queries.namespace, 'fetchMore')
+          .mockImplementation(jest.fn().mockResolvedValue({}));
+
+        await waitForPromises();
       });
 
-      it('contains correct `first` and `last` values when clicking "Prev" button', () => {
+      it('contains correct `first` and `last` values when clicking "Prev" button', async () => {
         findPrevButton().trigger('click');
-        expect($apollo.queries.namespace.fetchMore).toHaveBeenCalledWith(
+        expect(wrapper.vm.$apollo.queries.namespace.fetchMore).toHaveBeenCalledWith(
           expect.objectContaining({
             variables: expect.objectContaining({ first: undefined, last: expect.any(Number) }),
           }),
@@ -330,24 +345,46 @@ describe('NamespaceStorageApp', () => {
 
       it('contains `first` value when clicking "Next" button', () => {
         findNextButton().trigger('click');
-        expect($apollo.queries.namespace.fetchMore).toHaveBeenCalledWith(
+        expect(wrapper.vm.$apollo.queries.namespace.fetchMore).toHaveBeenCalledWith(
           expect.objectContaining({
             variables: expect.objectContaining({ first: expect.any(Number) }),
           }),
         );
       });
     });
+
+    describe('handling failed apollo requests', () => {
+      beforeEach(async () => {
+        mockApollo = createFailedMockApolloProvider();
+        createComponent({ mockApollo });
+
+        await waitForPromises();
+      });
+
+      it('shows gl-alert with error message', () => {
+        expect(findAlert().exists()).toBe(true);
+        expect(findAlert().text()).toBe('Something went wrong while loading usage details');
+      });
+
+      it('captures the exception in Sentry', async () => {
+        await Vue.nextTick();
+        expect(captureException).toHaveBeenCalledTimes(1);
+      });
+    });
   });
 
   describe('new storage statistics usage design', () => {
     describe('when namespace is on free plan', () => {
-      beforeEach(() => {
+      beforeEach(async () => {
+        mockApollo = createMockApolloProvider();
+
         createComponent({
           additionalRepoStorageByNamespace: true,
-          namespace: withRootStorageStatistics,
           storageLimitEnforced: true,
           isFreeNamespace: true,
+          mockApollo,
         });
+        await waitForPromises();
       });
 
       it('renders the new storage design', () => {
@@ -360,19 +397,52 @@ describe('NamespaceStorageApp', () => {
 
       it('passes storageSize as totalRepositorySize', () => {
         expect(findStorageUsageStatistics().props('totalRepositorySize')).toBe(
-          withRootStorageStatistics.rootStorageStatistics.storageSize,
+          mockedNamespaceStorageResponse.data.namespace.rootStorageStatistics.storageSize,
+        );
+      });
+
+      describe('loading', () => {
+        it.each`
+          loadingError | queryLoading | expectedValue
+          ${true}      | ${false}     | ${true}
+          ${false}     | ${true}      | ${true}
+          ${false}     | ${false}     | ${false}
+        `(
+          'pass loading prop as $expectedValue if loadingError is $loadingError and queryLoading is $queryLoading',
+          async ({ loadingError, queryLoading, expectedValue }) => {
+            // change mockApollo provider based on loadingError and queryLoading
+            if (loadingError) {
+              mockApollo = createFailedMockApolloProvider();
+            } else if (queryLoading) {
+              mockApollo = createPendingMockApolloProvider();
+            } else {
+              mockApollo = createMockApolloProvider();
+            }
+
+            createComponent({
+              additionalRepoStorageByNamespace: true,
+              storageLimitEnforced: true,
+              isFreeNamespace: true,
+              mockApollo,
+            });
+
+            await waitForPromises();
+
+            expect(findStorageUsageStatistics().props('loading')).toBe(expectedValue);
+          },
         );
       });
     });
 
     describe('when namespace is not on free plan', () => {
-      beforeEach(() => {
+      beforeEach(async () => {
         createComponent({
           additionalRepoStorageByNamespace: true,
-          namespace: withRootStorageStatistics,
+          mockApollo,
           storageLimitEnforced: true,
           isFreeNamespace: false,
         });
+        await waitForPromises();
       });
 
       it('does not render the new storage design', () => {
@@ -380,4 +450,70 @@ describe('NamespaceStorageApp', () => {
       });
     });
   });
+
+  describe('with rootStorageStatistics available on namespace', () => {
+    beforeEach(async () => {
+      mockApollo = createMockApolloProvider();
+      createComponent({ mockApollo });
+      await waitForPromises();
+    });
+
+    it('renders total usage', async () => {
+      expect(findTotalUsage().text()).toContain(
+        numberToHumanSize(
+          mockedNamespaceStorageResponse.data.namespace.rootStorageStatistics.storageSize,
+        ),
+      );
+    });
+
+    describe('with additional_repo_storage_by_namespace feature', () => {
+      it('usage_graph component hidden is when feature is false', async () => {
+        expect(findUsageGraph().exists()).toBe(true);
+        expect(findUsageStatistics().exists()).toBe(false);
+        expect(findStorageInlineAlert().exists()).toBe(false);
+      });
+
+      it('usage_statistics component is rendered when feature is true', async () => {
+        mockApollo = createMockApolloProvider();
+
+        createComponent({
+          mockApollo,
+          additionalRepoStorageByNamespace: true,
+        });
+        await waitForPromises();
+
+        expect(findUsageStatistics().exists()).toBe(true);
+        expect(findUsageGraph().exists()).toBe(false);
+        expect(findStorageInlineAlert().exists()).toBe(true);
+      });
+    });
+
+    describe('findStorageInlineAlert', () => {
+      it('does not show storage inline alert if namespace is empty', async () => {
+        // creating failed mock provider will make namespace = {}
+        mockApollo = createFailedMockApolloProvider();
+        createComponent({
+          additionalRepoStorageByNamespace: true,
+          storageLimitEnforced: true,
+          isFreeNamespace: true,
+          mockApollo,
+        });
+
+        await waitForPromises();
+        expect(findStorageInlineAlert().exists()).toBe(false);
+      });
+    });
+  });
+
+  describe('without rootStorageStatistics available on namespace', () => {
+    it('renders Not applicable for totalUsage when no rootStorageStatistics is provided', async () => {
+      const namespaceWithoutStatistics = { ...mockedNamespaceStorageResponse };
+      namespaceWithoutStatistics.data.namespace.rootStorageStatistics = null;
+      mockApollo = createMockApolloProvider(namespaceWithoutStatistics);
+      createComponent({ mockApollo });
+      await waitForPromises();
+
+      expect(findTotalUsage().text()).toContain('Not applicable');
+    });
+  });
 });
diff --git a/ee/spec/frontend/usage_quotas/storage/components/storage_statistics_card_spec.js b/ee/spec/frontend/usage_quotas/storage/components/storage_statistics_card_spec.js
index eb87000a2a11b13bc334f7649c8e2a907d8a0fce..2bfe9855eabe4a549725cf6493a0ad9e0d45326e 100644
--- a/ee/spec/frontend/usage_quotas/storage/components/storage_statistics_card_spec.js
+++ b/ee/spec/frontend/usage_quotas/storage/components/storage_statistics_card_spec.js
@@ -1,4 +1,4 @@
-import { GlProgressBar } from '@gitlab/ui';
+import { GlProgressBar, GlSkeletonLoader } from '@gitlab/ui';
 import StorageStatisticsCard from 'ee/usage_quotas/storage/components/storage_statistics_card.vue';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import { statisticsCardDefaultProps } from '../mock_data';
@@ -21,6 +21,11 @@ describe('StorageStatisticsCard', () => {
   const findDescriptionBlock = () => wrapper.findByTestId('description');
   const findActionsBlock = () => wrapper.findByTestId('actions');
   const findProgressBar = () => wrapper.findComponent(GlProgressBar);
+  const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
+
+  afterEach(() => {
+    wrapper.destroy();
+  });
 
   describe('denominator block', () => {
     it('renders denominator block with all elements when all props are passed', () => {
@@ -97,4 +102,16 @@ describe('StorageStatisticsCard', () => {
       expect(findProgressBar().attributes('value')).toBe(String(50));
     });
   });
+
+  describe('skeleton loader', () => {
+    it('renders skeleton loader when loading prop is true', () => {
+      createComponent({ loading: true });
+      expect(findSkeletonLoader().exists()).toBe(true);
+    });
+
+    it('does not render skeleton loader when loading prop is false', () => {
+      createComponent({ loading: false });
+      expect(findSkeletonLoader().exists()).toBe(false);
+    });
+  });
 });
diff --git a/ee/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js b/ee/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js
index 4a84c78da361872e90d51ee5bcd8f89e8e5b6dfc..eb58e5a6b372dae425d6fc1607918a4f100dc3b9 100644
--- a/ee/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js
+++ b/ee/spec/frontend/usage_quotas/storage/components/storage_usage_statistics_spec.js
@@ -16,6 +16,7 @@ describe('StorageUsageStatistics', () => {
         totalRepositorySizeExcess: withRootStorageStatistics.totalRepositorySizeExcess,
         additionalPurchasedStorageSize: withRootStorageStatistics.additionalPurchasedStorageSize,
         storageLimitEnforced: true,
+        loading: false,
         ...props,
       },
       provide: {
@@ -51,6 +52,7 @@ describe('StorageUsageStatistics', () => {
       createComponent();
 
       expect(findNamespaceStorageCard().props()).toEqual({
+        loading: false,
         showProgressBar: true,
         totalStorage: withRootStorageStatistics.totalRepositorySize,
         usedStorage: withRootStorageStatistics.actualRepositorySizeLimit,
@@ -61,6 +63,7 @@ describe('StorageUsageStatistics', () => {
       createComponent({ props: { storageLimitEnforced: true } });
 
       expect(findNamespaceStorageCard().props()).toEqual({
+        loading: false,
         showProgressBar: true,
         totalStorage: withRootStorageStatistics.totalRepositorySize,
         usedStorage: withRootStorageStatistics.totalRepositorySize,
@@ -71,6 +74,7 @@ describe('StorageUsageStatistics', () => {
       createComponent({ props: { storageLimitEnforced: false } });
 
       expect(findNamespaceStorageCard().props()).toEqual({
+        loading: false,
         showProgressBar: false,
         totalStorage: null,
         usedStorage: withRootStorageStatistics.totalRepositorySize,
@@ -101,6 +105,7 @@ describe('StorageUsageStatistics', () => {
     it('passes the correct props when storageLimitEnforced is true', () => {
       createComponent({ props: { storageLimitEnforced: true } });
       expect(findPurchasedStorageCard().props()).toEqual({
+        loading: false,
         showProgressBar: true,
         totalStorage: withRootStorageStatistics.additionalPurchasedStorageSize,
         usedStorage: withRootStorageStatistics.totalRepositorySizeExcess,
@@ -109,6 +114,7 @@ describe('StorageUsageStatistics', () => {
     it('passes the correct props when storageLimitEnforced is false', () => {
       createComponent({ props: { storageLimitEnforced: false } });
       expect(findPurchasedStorageCard().props()).toEqual({
+        loading: false,
         showProgressBar: false,
         totalStorage: withRootStorageStatistics.additionalPurchasedStorageSize,
         usedStorage: withRootStorageStatistics.totalRepositorySizeExcess,
diff --git a/ee/spec/frontend/usage_quotas/storage/mock_data.js b/ee/spec/frontend/usage_quotas/storage/mock_data.js
index 265afbe8821b6ae15642e5bf1ff5b147f9a65a52..c74770f223adfb5e2d5e93a929d326353b9d9e06 100644
--- a/ee/spec/frontend/usage_quotas/storage/mock_data.js
+++ b/ee/spec/frontend/usage_quotas/storage/mock_data.js
@@ -20,10 +20,14 @@ export const projects = [
       lfsObjectsSize: 0,
       buildArtifactsSize: 0,
       packagesSize: 0,
+      wikiSize: 104800,
+      snippetsSize: 0,
+      uploadsSize: 0,
     },
     actualRepositorySizeLimit: 100000,
     totalCalculatedUsedStorage: 41943,
     totalCalculatedStorageLimit: 41943000,
+    repositorySizeExcess: 0,
   },
   {
     id: '8',
@@ -40,10 +44,14 @@ export const projects = [
       lfsObjectsSize: 0,
       buildArtifactsSize: 1272375,
       packagesSize: 0,
+      wikiSize: 104800,
+      snippetsSize: 0,
+      uploadsSize: 0,
     },
     actualRepositorySizeLimit: 100000,
     totalCalculatedUsedStorage: 89000,
     totalCalculatedStorageLimit: 99430,
+    repositorySizeExcess: 0,
   },
   {
     id: '80',
@@ -60,10 +68,14 @@ export const projects = [
       lfsObjectsSize: 209720,
       buildArtifactsSize: 1272375,
       packagesSize: 0,
+      wikiSize: 104800,
+      snippetsSize: 0,
+      uploadsSize: 0,
     },
     actualRepositorySizeLimit: 100000,
     totalCalculatedUsedStorage: 13143170,
     totalCalculatedStorageLimit: 12143170,
+    repositorySizeExcess: 0,
   },
 ];
 
@@ -209,4 +221,105 @@ export const statisticsCardDefaultProps = {
   totalStorage: 100 * 1024,
   usedStorage: 50 * 1024,
   hideProgressBar: false,
+  loading: false,
+};
+
+export const mockedNamespaceStorageResponse = {
+  data: {
+    namespace: {
+      id: 'gid://gitlab/Group/84',
+      name: 'wandercatgroup2',
+      storageSizeLimit: 0,
+      actualRepositorySizeLimit: 10737418240,
+      additionalPurchasedStorageSize: 0,
+      totalRepositorySizeExcess: 0,
+      totalRepositorySize: 20971,
+      containsLockedProjects: false,
+      repositorySizeExcessProjectCount: 0,
+      rootStorageStatistics: {
+        containerRegistrySize: 3_900_000,
+        storageSize: 125771,
+        repositorySize: 20971,
+        lfsObjectsSize: 0,
+        buildArtifactsSize: 0,
+        pipelineArtifactsSize: 0,
+        packagesSize: 0,
+        wikiSize: 104800,
+        snippetsSize: 0,
+        uploadsSize: 0,
+        __typename: 'RootStorageStatistics',
+      },
+      projects: {
+        nodes: [
+          {
+            id: 'gid://gitlab/Project/20',
+            fullPath: 'wandercatgroup2/not-so-empty-project',
+            nameWithNamespace: 'wandercatgroup2 / not so empty project',
+            avatarUrl: null,
+            webUrl: 'http://gdk.test:3000/wandercatgroup2/not-so-empty-project',
+            name: 'not so empty project',
+            repositorySizeExcess: 0,
+            actualRepositorySizeLimit: 10737418240,
+            statistics: {
+              commitCount: 1,
+              storageSize: 125771,
+              repositorySize: 20971,
+              lfsObjectsSize: 0,
+              containerRegistrySize: 0,
+              buildArtifactsSize: 0,
+              packagesSize: 0,
+              wikiSize: 104800,
+              snippetsSize: 0,
+              uploadsSize: 0,
+              __typename: 'ProjectStatistics',
+            },
+            __typename: 'Project',
+          },
+          {
+            id: 'gid://gitlab/Project/21',
+            fullPath: 'wandercatgroup2/not-so-empty-project1',
+            nameWithNamespace: 'wandercatgroup2 / not so empty project',
+            avatarUrl: null,
+            webUrl: 'http://gdk.test:3000/wandercatgroup2/not-so-empty-project',
+            name: 'not so empty project',
+            repositorySizeExcess: 0,
+            actualRepositorySizeLimit: 10737418240,
+            statistics: {
+              commitCount: 1,
+              storageSize: 125771,
+              repositorySize: 20971,
+              lfsObjectsSize: 0,
+              containerRegistrySize: 0,
+              buildArtifactsSize: 0,
+              packagesSize: 0,
+              wikiSize: 104800,
+              snippetsSize: 0,
+              uploadsSize: 0,
+              __typename: 'ProjectStatistics',
+            },
+            __typename: 'Project',
+          },
+        ],
+        pageInfo: {
+          __typename: 'PageInfo',
+          hasNextPage: false,
+          hasPreviousPage: false,
+          startCursor: 'eyJpZCI6IjIwIiwiZXhjZXNzX3N0b3JhZ2UiOiItMTA3MzczOTcyNjkifQ',
+          endCursor: 'eyJpZCI6IjIwIiwiZXhjZXNzX3N0b3JhZ2UiOiItMTA3MzczOTcyNjkifQ',
+        },
+        __typename: 'ProjectConnection',
+      },
+      __typename: 'Namespace',
+    },
+  },
+};
+
+export const mockDependencyProxyResponse = {
+  data: {
+    group: {
+      id: 'gid://gitlab/Group/84',
+      dependencyProxyTotalSize: '0 Bytes',
+      __typename: 'Group',
+    },
+  },
 };
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d3e55ff9df31061e98df5fbd5b658502e4a21c79..0c43c754c865f29eb03f098db825b3953ff5a96f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -41232,6 +41232,9 @@ msgstr ""
 msgid "UsageQuota|Something went wrong while fetching project storage statistics"
 msgstr ""
 
+msgid "UsageQuota|Something went wrong while loading usage details"
+msgstr ""
+
 msgid "UsageQuota|Storage"
 msgstr ""