diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue
index 422dee25401e521a56399a77aedb115f3ad83aa5..e4a9892912f312f4feddc347fa6fc57dd1b8f647 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue
@@ -14,7 +14,7 @@ import UrlSync from '~/vue_shared/components/url_sync.vue';
 import PageHeading from '~/vue_shared/components/page_heading.vue';
 import { METRICS_REQUESTS } from '../constants';
 import DurationChart from './duration_chart.vue';
-import TypeOfWorkCharts from './type_of_work_charts.vue';
+import TypeOfWorkChartsLoader from './type_of_work_charts_loader.vue';
 import ValueStreamAggregationStatus from './value_stream_aggregation_status.vue';
 import ValueStreamAggregatingWarning from './value_stream_aggregating_warning.vue';
 import ValueStreamEmptyState from './value_stream_empty_state.vue';
@@ -27,7 +27,7 @@ export default {
     PageHeading,
     DurationChart,
     GlEmptyState,
-    TypeOfWorkCharts,
+    TypeOfWorkChartsLoader,
     StageTable,
     PathNavigation,
     ValueStreamAggregationStatus,
@@ -271,7 +271,7 @@ export default {
         <div :class="[isOverviewStageSelected ? 'gl-mt-2' : 'gl-mt-6']">
           <duration-overview-chart v-if="isOverviewStageSelected" class="gl-mb-6" />
           <duration-chart v-else class="gl-mb-6" />
-          <type-of-work-charts
+          <type-of-work-charts-loader
             v-if="enableTasksByTypeChart"
             v-show="isOverviewStageSelected"
             class="gl-mb-6"
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.stories.js b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.stories.js
index 41fec714fbba19284eed22e9c726e2aa77989b50..fbe28a3c607227338a63296e3051de08f79465e4 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.stories.js
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.stories.js
@@ -1,6 +1,6 @@
 import { withVuexStore } from 'storybook_addons/vuex_store';
 import TypeOfWorkCharts from './type_of_work_charts.vue';
-import { defaultGroupLabels } from './stories_constants';
+import { tasksByTypeChartData, defaultGroupLabels } from './stories_constants';
 
 export default {
   component: TypeOfWorkCharts,
@@ -8,7 +8,7 @@ export default {
   decorators: [withVuexStore],
 };
 
-const createStoryWithState = ({ typeOfWork: { getters, state } = {} }) => {
+const createStoryWithState = ({ state = {} }) => {
   return (args, { argTypes, createVuexStore }) => ({
     components: { TypeOfWorkCharts },
     props: Object.keys(argTypes),
@@ -22,31 +22,13 @@ const createStoryWithState = ({ typeOfWork: { getters, state } = {} }) => {
       },
       getters: {
         selectedProjectIds: () => [],
-        cycleAnalyticsRequestParams: () => ({
-          project_ids: null,
-          created_after: '2024-01-01',
-          created_before: '2024-03-01',
-          author_username: null,
-          milestone_title: null,
-          assignee_username: null,
-        }),
       },
       modules: {
         typeOfWork: {
           namespaced: true,
+          state,
           getters: {
             selectedLabelNames: () => [],
-            ...getters,
-          },
-          state: {
-            isLoading: false,
-            errorMessage: null,
-            topRankedLabels: [],
-            ...state,
-          },
-          actions: {
-            fetchTopRankedGroupLabels: () => {},
-            setTasksByTypeFilters: () => {},
           },
         },
       },
@@ -54,21 +36,13 @@ const createStoryWithState = ({ typeOfWork: { getters, state } = {} }) => {
   });
 };
 
-const defaultState = {};
-export const Default = createStoryWithState(defaultState).bind({});
+export const Default = createStoryWithState({}).bind({});
+Default.args = { chartData: tasksByTypeChartData };
 
-const noDataState = { typeOfWork: { state: { data: [] } } };
-export const NoData = createStoryWithState(noDataState).bind({});
+export const NoData = createStoryWithState({}).bind({});
+NoData.args = { chartData: { data: [] } };
 
-const isLoadingState = {
-  typeOfWork: { state: { isLoading: true } },
-};
-export const IsLoading = createStoryWithState(isLoadingState).bind({});
-
-const errorState = {
-  typeOfWork: {
-    ...noDataState.typeOfWork,
-    state: { errorMessage: 'Failed to load chart' },
-  },
-};
-export const ErrorMessage = createStoryWithState(errorState).bind({});
+export const ErrorMessage = createStoryWithState({
+  state: { errorMessage: 'Failed to load chart' },
+}).bind({});
+ErrorMessage.args = { chartData: { data: [] } };
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
index 762322b093ef061e4faced6ca0a93f40b2e5fe47..cc472ac21224afd185db7f27621dec2fab99d3e6 100644
--- a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts.vue
@@ -1,18 +1,9 @@
 <script>
 // eslint-disable-next-line no-restricted-imports
-import { mapActions, mapGetters, mapState } from 'vuex';
+import { mapGetters, mapState } from 'vuex';
 import { GlAlert, GlIcon, GlTooltip } from '@gitlab/ui';
-import { __ } from '~/locale';
 import SafeHtml from '~/vue_shared/directives/safe_html';
-import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
-import { getTypeOfWorkTasksByType } from 'ee/api/analytics_api';
-import {
-  generateFilterTextDescription,
-  getTasksByTypeData,
-  checkForDataError,
-  alertErrorIfStatusNotOk,
-  transformRawTasksByTypeData,
-} from '../utils';
+import { generateFilterTextDescription } from '../utils';
 import { formattedDate } from '../../shared/utils';
 import { TASKS_BY_TYPE_SUBJECT_ISSUE, TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS } from '../constants';
 import TasksByTypeChart from './tasks_by_type/chart.vue';
@@ -22,7 +13,6 @@ import NoDataAvailableState from './no_data_available_state.vue';
 export default {
   name: 'TypeOfWorkCharts',
   components: {
-    ChartSkeletonLoader,
     GlAlert,
     GlIcon,
     GlTooltip,
@@ -33,29 +23,20 @@ export default {
   directives: {
     SafeHtml,
   },
-  data() {
-    return {
-      tasksByType: [],
-      isLoadingTasksByType: false,
-    };
+  props: {
+    chartData: {
+      type: Object,
+      required: true,
+    },
   },
   computed: {
     ...mapState(['namespace', 'createdAfter', 'createdBefore']),
-    ...mapState('typeOfWork', ['subject', 'errorMessage', 'isLoading']),
-    ...mapGetters(['selectedProjectIds', 'cycleAnalyticsRequestParams']),
+    ...mapState('typeOfWork', ['subject', 'errorMessage']),
+    ...mapGetters(['selectedProjectIds']),
     ...mapGetters('typeOfWork', ['selectedLabelNames']),
-    chartData() {
-      const { tasksByType, createdAfter, createdBefore } = this;
-      return tasksByType.length
-        ? getTasksByTypeData({ data: tasksByType, createdAfter, createdBefore })
-        : { groupBy: [], data: [] };
-    },
     hasData() {
       return Boolean(this.chartData?.data.length);
     },
-    hasError() {
-      return this.errorMessage && this.errorMessage !== '';
-    },
     tooltipText() {
       return generateFilterTextDescription({
         groupName: this.namespace.name,
@@ -76,96 +57,31 @@ export default {
         TASKS_BY_TYPE_SUBJECT_FILTER_OPTIONS[TASKS_BY_TYPE_SUBJECT_ISSUE]
       );
     },
-    tasksByTypeParams() {
-      const {
-        subject,
-        selectedLabelNames,
-        cycleAnalyticsRequestParams: {
-          project_ids,
-          created_after,
-          created_before,
-          author_username,
-          milestone_title,
-          assignee_username,
-        },
-      } = this;
-      return {
-        project_ids,
-        created_after,
-        created_before,
-        author_username,
-        milestone_title,
-        assignee_username,
-        subject,
-        label_names: selectedLabelNames,
-      };
-    },
-  },
-  async created() {
-    await this.fetchTopRankedGroupLabels();
-
-    if (!this.hasError) {
-      this.fetchTasksByType();
-    }
-  },
-  methods: {
-    ...mapActions('typeOfWork', ['fetchTopRankedGroupLabels', 'setTasksByTypeFilters']),
-    onUpdateFilter(e) {
-      this.setTasksByTypeFilters(e);
-      this.fetchTasksByType();
-    },
-    fetchTasksByType() {
-      // dont request if we have no labels selected
-      if (!this.selectedLabelNames.length) {
-        this.tasksByType = [];
-        return;
-      }
-
-      this.isLoadingTasksByType = true;
-
-      getTypeOfWorkTasksByType(this.namespace.fullPath, this.tasksByTypeParams)
-        .then(checkForDataError)
-        .then(({ data }) => {
-          this.tasksByType = transformRawTasksByTypeData(data);
-        })
-        .catch((error) => {
-          alertErrorIfStatusNotOk({
-            error,
-            message: __('There was an error fetching data for the tasks by type chart'),
-          });
-        })
-        .finally(() => {
-          this.isLoadingTasksByType = false;
-        });
-    },
   },
 };
 </script>
 <template>
-  <div class="js-tasks-by-type-chart">
-    <chart-skeleton-loader v-if="isLoading || isLoadingTasksByType" class="gl-my-4 gl-py-4" />
-    <div v-else>
-      <div class="gl-flex gl-justify-between">
-        <h4 class="gl-mt-0">
-          {{ s__('ValueStreamAnalytics|Tasks by type') }}&nbsp;
-          <span ref="tooltipTrigger" data-testid="vsa-task-by-type-description">
-            <gl-icon name="information-o" />
-          </span>
-          <gl-tooltip :target="() => $refs.tooltipTrigger" boundary="viewport" placement="top">
-            <span v-safe-html="tooltipText"></span>
-          </gl-tooltip>
-        </h4>
-        <tasks-by-type-filters
-          :selected-label-names="selectedLabelNames"
-          :subject-filter="selectedSubjectFilter"
-          @update-filter="onUpdateFilter"
-        />
-      </div>
-      <tasks-by-type-chart v-if="hasData" :data="chartData.data" :group-by="chartData.groupBy" />
-      <gl-alert v-else-if="errorMessage" variant="info" :dismissible="false" class="gl-mt-3">
-        {{ errorMessage }}
-      </gl-alert>
-      <no-data-available-state v-else />
+  <div>
+    <div class="gl-flex gl-justify-between">
+      <h4 class="gl-mt-0">
+        {{ s__('ValueStreamAnalytics|Tasks by type') }}&nbsp;
+        <span ref="tooltipTrigger" data-testid="vsa-task-by-type-description">
+          <gl-icon name="information-o" />
+        </span>
+        <gl-tooltip :target="() => $refs.tooltipTrigger" boundary="viewport" placement="top">
+          <span v-safe-html="tooltipText"></span>
+        </gl-tooltip>
+      </h4>
+      <tasks-by-type-filters
+        :selected-label-names="selectedLabelNames"
+        :subject-filter="selectedSubjectFilter"
+        @update-filter="$emit('update-filter', $event)"
+      />
     </div>
+    <tasks-by-type-chart v-if="hasData" :data="chartData.data" :group-by="chartData.groupBy" />
+    <gl-alert v-else-if="errorMessage" variant="info" :dismissible="false" class="gl-mt-3">
+      {{ errorMessage }}
+    </gl-alert>
+    <no-data-available-state v-else />
   </div>
 </template>
diff --git a/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts_loader.vue b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts_loader.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a4f8d3781f564ac44ad806ed09827e5709905a85
--- /dev/null
+++ b/ee/app/assets/javascripts/analytics/cycle_analytics/components/type_of_work_charts_loader.vue
@@ -0,0 +1,111 @@
+<script>
+// eslint-disable-next-line no-restricted-imports
+import { mapActions, mapGetters, mapState } from 'vuex';
+import { __ } from '~/locale';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { getTypeOfWorkTasksByType } from 'ee/api/analytics_api';
+import {
+  getTasksByTypeData,
+  checkForDataError,
+  alertErrorIfStatusNotOk,
+  transformRawTasksByTypeData,
+} from '../utils';
+import TypeOfWorkCharts from './type_of_work_charts.vue';
+
+export default {
+  name: 'TypeOfWorkChartsLoader',
+  components: {
+    ChartSkeletonLoader,
+    TypeOfWorkCharts,
+  },
+  data() {
+    return {
+      tasksByType: [],
+      isLoadingTasksByType: false,
+    };
+  },
+  computed: {
+    ...mapState(['namespace', 'createdAfter', 'createdBefore']),
+    ...mapState('typeOfWork', ['subject', 'errorMessage', 'isLoading']),
+    ...mapGetters(['cycleAnalyticsRequestParams']),
+    ...mapGetters('typeOfWork', ['selectedLabelNames']),
+    chartData() {
+      const { tasksByType, createdAfter, createdBefore } = this;
+      return tasksByType.length
+        ? getTasksByTypeData({ data: tasksByType, createdAfter, createdBefore })
+        : { groupBy: [], data: [] };
+    },
+    hasError() {
+      return this.errorMessage && this.errorMessage !== '';
+    },
+    tasksByTypeParams() {
+      const {
+        subject,
+        selectedLabelNames,
+        cycleAnalyticsRequestParams: {
+          project_ids,
+          created_after,
+          created_before,
+          author_username,
+          milestone_title,
+          assignee_username,
+        },
+      } = this;
+      return {
+        project_ids,
+        created_after,
+        created_before,
+        author_username,
+        milestone_title,
+        assignee_username,
+        subject,
+        label_names: selectedLabelNames,
+      };
+    },
+  },
+  async created() {
+    await this.fetchTopRankedGroupLabels();
+
+    if (!this.hasError) {
+      this.fetchTasksByType();
+    }
+  },
+  methods: {
+    ...mapActions('typeOfWork', ['fetchTopRankedGroupLabels', 'setTasksByTypeFilters']),
+    onUpdateFilter(e) {
+      this.setTasksByTypeFilters(e);
+      this.fetchTasksByType();
+    },
+    fetchTasksByType() {
+      // dont request if we have no labels selected
+      if (!this.selectedLabelNames.length) {
+        this.tasksByType = [];
+        return;
+      }
+
+      this.isLoadingTasksByType = true;
+
+      getTypeOfWorkTasksByType(this.namespace.fullPath, this.tasksByTypeParams)
+        .then(checkForDataError)
+        .then(({ data }) => {
+          this.tasksByType = transformRawTasksByTypeData(data);
+        })
+        .catch((error) => {
+          alertErrorIfStatusNotOk({
+            error,
+            message: __('There was an error fetching data for the tasks by type chart'),
+          });
+        })
+        .finally(() => {
+          this.isLoadingTasksByType = false;
+        });
+    },
+  },
+};
+</script>
+<template>
+  <div class="js-tasks-by-type-chart">
+    <chart-skeleton-loader v-if="isLoading || isLoadingTasksByType" class="gl-my-4 gl-py-4" />
+    <type-of-work-charts v-else :chart-data="chartData" @update-filter="onUpdateFilter" />
+  </div>
+</template>
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js
index 96df488569673f6ef0e9f380bdcec90b26d28c04..2ff3af857077818db2b3ff4d607bc2ff16898721 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/components/base_spec.js
@@ -8,7 +8,7 @@ import Vuex from 'vuex';
 import Component from 'ee/analytics/cycle_analytics/components/base.vue';
 import DurationChart from 'ee/analytics/cycle_analytics/components/duration_chart.vue';
 import DurationOverviewChart from 'ee/analytics/cycle_analytics/components/duration_overview_chart.vue';
-import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
+import TypeOfWorkChartsLoader from 'ee/analytics/cycle_analytics/components/type_of_work_charts_loader.vue';
 import ValueStreamSelect from 'ee/analytics/cycle_analytics/components/value_stream_select.vue';
 import ValueStreamAggregationStatus from 'ee/analytics/cycle_analytics/components/value_stream_aggregation_status.vue';
 import ValueStreamEmptyState from 'ee/analytics/cycle_analytics/components/value_stream_empty_state.vue';
@@ -188,7 +188,7 @@ describe('EE Value Stream Analytics component', () => {
   const findFilterBar = () => wrapper.findComponent(ValueStreamFilters);
   const findDurationChart = () => wrapper.findComponent(DurationChart);
   const findDurationOverviewChart = () => wrapper.findComponent(DurationOverviewChart);
-  const findTypeOfWorkCharts = () => wrapper.findComponent(TypeOfWorkCharts);
+  const findTypeOfWorkCharts = () => wrapper.findComponent(TypeOfWorkChartsLoader);
   const findValueStreamSelect = () => wrapper.findComponent(ValueStreamSelect);
   const findUrlSync = () => wrapper.findComponent(UrlSync);
 
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_loader_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_loader_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..0b962c67e7728274b8f85b102b53a9fb056ba048
--- /dev/null
+++ b/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_loader_spec.js
@@ -0,0 +1,187 @@
+import { shallowMount } from '@vue/test-utils';
+import axios from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import Vue from 'vue';
+// eslint-disable-next-line no-restricted-imports
+import Vuex from 'vuex';
+import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+import waitForPromises from 'helpers/wait_for_promises';
+import TypeOfWorkChartsLoader from 'ee/analytics/cycle_analytics/components/type_of_work_charts_loader.vue';
+import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
+import typeOfWorkModule from 'ee/analytics/cycle_analytics/store/modules/type_of_work';
+import {
+  TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST,
+  TASKS_BY_TYPE_FILTERS,
+  TASKS_BY_TYPE_SUBJECT_ISSUE,
+} from 'ee/analytics/cycle_analytics/constants';
+import { createAlert } from '~/alert';
+import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
+import { rawTasksByTypeData, groupLabelNames, endpoints } from '../mock_data';
+
+Vue.use(Vuex);
+jest.mock('~/alert');
+
+describe('TypeOfWorkChartsLoader', () => {
+  let wrapper;
+  let mock;
+
+  const fetchTopRankedGroupLabels = jest.fn();
+  const setTasksByTypeFilters = jest.fn();
+
+  const cycleAnalyticsRequestParams = {
+    project_ids: null,
+    created_after: '2019-12-11',
+    created_before: '2020-01-10',
+    author_username: null,
+    milestone_title: null,
+    assignee_username: null,
+  };
+
+  const createStore = (state) =>
+    new Vuex.Store({
+      state: {
+        namespace: {
+          fullPath: 'fake/group/path',
+        },
+        createdAfter: new Date('2019-12-11'),
+        createdBefore: new Date('2020-01-10'),
+      },
+      getters: {
+        cycleAnalyticsRequestParams: () => cycleAnalyticsRequestParams,
+      },
+      modules: {
+        typeOfWork: {
+          ...typeOfWorkModule,
+          state: {
+            subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
+            ...typeOfWorkModule.state,
+            ...state,
+          },
+          getters: {
+            ...typeOfWorkModule.getters,
+            selectedLabelNames: () => groupLabelNames,
+          },
+          actions: {
+            ...typeOfWorkModule.actions,
+            fetchTopRankedGroupLabels,
+            setTasksByTypeFilters,
+          },
+        },
+      },
+    });
+
+  const createWrapper = ({ state = {} } = {}) => {
+    wrapper = shallowMount(TypeOfWorkChartsLoader, {
+      store: createStore(state),
+    });
+
+    return waitForPromises();
+  };
+
+  const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
+  const findTypeOfWorkCharts = () => wrapper.findComponent(TypeOfWorkCharts);
+
+  beforeEach(() => {
+    mock = new MockAdapter(axios);
+  });
+
+  afterEach(() => {
+    mock.restore();
+  });
+
+  describe('when loading', () => {
+    beforeEach(() => {
+      createWrapper({ state: { isLoading: true } });
+    });
+
+    it('renders skeleton loader', () => {
+      expect(findLoader().exists()).toBe(true);
+    });
+  });
+
+  describe('with data', () => {
+    beforeEach(() => {
+      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_OK, rawTasksByTypeData);
+      return createWrapper();
+    });
+
+    it('calls the `fetchTopRankedGroupLabels` action', () => {
+      expect(fetchTopRankedGroupLabels).toHaveBeenCalled();
+    });
+
+    it('fetches tasks by type', () => {
+      expect(mock.history.get.length).toBe(1);
+      expect(mock.history.get[0]).toEqual(
+        expect.objectContaining({
+          url: '/fake/group/path/-/analytics/type_of_work/tasks_by_type',
+          params: {
+            ...cycleAnalyticsRequestParams,
+            subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
+            label_names: groupLabelNames,
+          },
+        }),
+      );
+    });
+
+    it('renders the type of work charts', () => {
+      expect(findTypeOfWorkCharts().exists()).toBe(true);
+    });
+
+    it('does not render the loading icon', () => {
+      expect(findLoader().exists()).toBe(false);
+    });
+
+    describe('when update filter is emitted', () => {
+      const payload = {
+        filter: TASKS_BY_TYPE_FILTERS.SUBJECT,
+        value: TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST,
+      };
+
+      beforeEach(() => {
+        findTypeOfWorkCharts(wrapper).vm.$emit('update-filter', payload);
+      });
+
+      it('calls the setTasksByTypeFilters method', () => {
+        expect(setTasksByTypeFilters).toHaveBeenCalledWith(expect.any(Object), payload);
+      });
+
+      it('refetches the tasks by type', () => {
+        expect(mock.history.get.length).toBe(2);
+        expect(mock.history.get[1]).toEqual(
+          expect.objectContaining({
+            url: '/fake/group/path/-/analytics/type_of_work/tasks_by_type',
+            params: {
+              ...cycleAnalyticsRequestParams,
+              subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
+              label_names: groupLabelNames,
+            },
+          }),
+        );
+      });
+    });
+  });
+
+  describe('when tasks by type returns 200 with a data error', () => {
+    beforeEach(() => {
+      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_OK, { error: 'Too much data' });
+      return createWrapper();
+    });
+
+    it('does not show an alert', () => {
+      expect(createAlert).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('when tasks by type throws an error', () => {
+    beforeEach(() => {
+      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_NOT_FOUND, { error: 'error' });
+      return createWrapper();
+    });
+
+    it('shows an error alert', () => {
+      expect(createAlert).toHaveBeenCalledWith({
+        message: 'There was an error fetching data for the tasks by type chart',
+      });
+    });
+  });
+});
diff --git a/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js b/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js
index 5d6e4807843c69948d567b37757f2971374ab91b..48860f42fd75d24d5931ca541c4d50e3c6b2c4e4 100644
--- a/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js
+++ b/ee/spec/frontend/analytics/cycle_analytics/components/type_of_work_charts_spec.js
@@ -1,10 +1,7 @@
 import { shallowMount } from '@vue/test-utils';
-import axios from 'axios';
-import MockAdapter from 'axios-mock-adapter';
 import Vue from 'vue';
 // eslint-disable-next-line no-restricted-imports
 import Vuex from 'vuex';
-import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';
 import waitForPromises from 'helpers/wait_for_promises';
 import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type/chart.vue';
 import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/filters.vue';
@@ -16,44 +13,24 @@ import {
   TASKS_BY_TYPE_FILTERS,
   TASKS_BY_TYPE_SUBJECT_ISSUE,
 } from 'ee/analytics/cycle_analytics/constants';
-import { createAlert } from '~/alert';
-import ChartSkeletonLoader from '~/vue_shared/components/resizable_chart/skeleton_loader.vue';
-import { rawTasksByTypeData, groupLabelNames, endpoints } from '../mock_data';
+import { tasksByTypeData, groupLabelNames } from '../mock_data';
 
 Vue.use(Vuex);
-jest.mock('~/alert');
 
 describe('TypeOfWorkCharts', () => {
   let wrapper;
-  let mock;
-
-  const fetchTopRankedGroupLabels = jest.fn();
-  const setTasksByTypeFilters = jest.fn();
-
-  const cycleAnalyticsRequestParams = {
-    project_ids: null,
-    created_after: '2019-12-11',
-    created_before: '2020-01-10',
-    author_username: null,
-    milestone_title: null,
-    assignee_username: null,
-  };
 
   const createStore = (state, rootGetters) =>
     new Vuex.Store({
       state: {
         namespace: {
-          fullPath: 'fake/group/path',
           name: 'Gitlab Org',
-          type: 'Group',
         },
         createdAfter: new Date('2019-12-11'),
         createdBefore: new Date('2020-01-10'),
       },
       getters: {
-        namespacePath: () => 'fake/group/path',
         selectedProjectIds: () => [],
-        cycleAnalyticsRequestParams: () => cycleAnalyticsRequestParams,
         ...rootGetters,
       },
       modules: {
@@ -68,22 +45,20 @@ describe('TypeOfWorkCharts', () => {
             ...typeOfWorkModule.getters,
             selectedLabelNames: () => groupLabelNames,
           },
-          actions: {
-            ...typeOfWorkModule.actions,
-            fetchTopRankedGroupLabels,
-            setTasksByTypeFilters,
-          },
         },
       },
     });
 
-  const createWrapper = ({ state = {}, rootGetters = {}, stubs = {} } = {}) => {
+  const createWrapper = ({ state = {}, rootGetters = {}, props = {} } = {}) => {
     wrapper = shallowMount(TypeOfWorkCharts, {
       store: createStore(state, rootGetters),
       stubs: {
         TasksByTypeChart: true,
         TasksByTypeFilters: true,
-        ...stubs,
+      },
+      propsData: {
+        chartData: tasksByTypeData,
+        ...props,
       },
     });
 
@@ -92,53 +67,18 @@ describe('TypeOfWorkCharts', () => {
 
   const findSubjectFilters = () => wrapper.findComponent(TasksByTypeFilters);
   const findTasksByTypeChart = () => wrapper.findComponent(TasksByTypeChart);
-  const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
   const findNoDataAvailableState = () => wrapper.findComponent(NoDataAvailableState);
 
-  beforeEach(() => {
-    mock = new MockAdapter(axios);
-  });
-
-  afterEach(() => {
-    mock.restore();
-  });
-
-  describe('when loading', () => {
-    beforeEach(() => {
-      createWrapper({ state: { isLoading: true } });
-    });
-
-    it('renders skeleton loader', () => {
-      expect(findLoader().exists()).toBe(true);
-    });
-  });
-
   describe('with data', () => {
     beforeEach(() => {
-      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_OK, rawTasksByTypeData);
-      return createWrapper();
-    });
-
-    it('calls the `fetchTopRankedGroupLabels` action', () => {
-      expect(fetchTopRankedGroupLabels).toHaveBeenCalled();
-    });
-
-    it('fetches tasks by type', () => {
-      expect(mock.history.get.length).toBe(1);
-      expect(mock.history.get[0]).toEqual(
-        expect.objectContaining({
-          url: '/fake/group/path/-/analytics/type_of_work/tasks_by_type',
-          params: {
-            ...cycleAnalyticsRequestParams,
-            subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
-            label_names: groupLabelNames,
-          },
-        }),
-      );
+      createWrapper();
     });
 
     it('renders the task by type chart', () => {
-      expect(findTasksByTypeChart().exists()).toBe(true);
+      expect(findTasksByTypeChart().props()).toEqual({
+        data: tasksByTypeData.data,
+        groupBy: tasksByTypeData.groupBy,
+      });
     });
 
     it('renders a description of the current filters', () => {
@@ -147,81 +87,39 @@ describe('TypeOfWorkCharts', () => {
       );
     });
 
-    it('does not render the loading icon', () => {
-      expect(findLoader(wrapper).exists()).toBe(false);
+    it('renders the subject filters', () => {
+      expect(findSubjectFilters().props()).toEqual(
+        expect.objectContaining({
+          selectedLabelNames: groupLabelNames,
+          subjectFilter: TASKS_BY_TYPE_SUBJECT_ISSUE,
+        }),
+      );
     });
 
-    describe('when a filter is selected', () => {
+    it('emits the update-filter when a filter is selected', () => {
       const payload = {
         filter: TASKS_BY_TYPE_FILTERS.SUBJECT,
         value: TASKS_BY_TYPE_SUBJECT_MERGE_REQUEST,
       };
 
-      beforeEach(() => {
-        findSubjectFilters(wrapper).vm.$emit('update-filter', payload);
-      });
-
-      it('calls the setTasksByTypeFilters method', () => {
-        expect(setTasksByTypeFilters).toHaveBeenCalledWith(expect.any(Object), payload);
-      });
+      findSubjectFilters().vm.$emit('update-filter', payload);
 
-      it('refetches the tasks by type', () => {
-        expect(mock.history.get.length).toBe(2);
-        expect(mock.history.get[1]).toEqual(
-          expect.objectContaining({
-            url: '/fake/group/path/-/analytics/type_of_work/tasks_by_type',
-            params: {
-              ...cycleAnalyticsRequestParams,
-              subject: TASKS_BY_TYPE_SUBJECT_ISSUE,
-              label_names: groupLabelNames,
-            },
-          }),
-        );
-      });
-    });
-  });
-
-  describe('when tasks by type returns 200 with a data error', () => {
-    beforeEach(() => {
-      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_OK, { error: 'Too much data' });
-      return createWrapper();
-    });
-
-    it('does not show an alert', () => {
-      expect(createAlert).not.toHaveBeenCalled();
-    });
-  });
-
-  describe('when tasks by type throws an error', () => {
-    beforeEach(() => {
-      mock.onGet(endpoints.tasksByTypeData).replyOnce(HTTP_STATUS_NOT_FOUND, { error: 'error' });
-      return createWrapper();
-    });
-
-    it('shows an error alert', () => {
-      expect(createAlert).toHaveBeenCalledWith({
-        message: 'There was an error fetching data for the tasks by type chart',
-      });
+      expect(wrapper.emitted('update-filter')[0][0]).toEqual(payload);
     });
   });
 
   describe('with selected projects', () => {
-    const createWithProjects = (projectIds) =>
-      createWrapper({
-        rootGetters: {
-          selectedProjectIds: () => projectIds,
-        },
-      });
+    it('renders multiple selected project counts', () => {
+      createWrapper({ rootGetters: { selectedProjectIds: () => [1, 2] } });
 
-    it('renders multiple selected project counts', async () => {
-      await createWithProjects([1, 2]);
       expect(wrapper.text()).toContain(
         "Shows issues and 3 labels for group 'Gitlab Org' and 2 projects from Dec 11, 2019 to Jan 10, 2020",
       );
     });
 
-    it('renders one selected project count', async () => {
-      await createWithProjects([1]);
+    it('renders one selected project count', () => {
+      createWrapper({ rootGetters: { selectedProjectIds: () => [1] } });
+
       expect(wrapper.text()).toContain(
         "Shows issues and 3 labels for group 'Gitlab Org' and 1 project from Dec 11, 2019 to Jan 10, 2020",
       );
@@ -230,15 +128,15 @@ describe('TypeOfWorkCharts', () => {
 
   describe('with no data', () => {
     beforeEach(() => {
-      return createWrapper({ state: { data: [] } });
+      createWrapper({ props: { chartData: { data: [] } } });
     });
 
     it('does not renders the task by type chart', () => {
-      expect(findTasksByTypeChart(wrapper).exists()).toBe(false);
+      expect(findTasksByTypeChart().exists()).toBe(false);
     });
 
     it('renders the no data available message', () => {
-      expect(findNoDataAvailableState(wrapper).exists()).toBe(true);
+      expect(findNoDataAvailableState().exists()).toBe(true);
     });
   });
 });