diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 40e9e0604bb55421cc6037f70456b8aefb21e9ac..37d3c95cd5894167b5a94045b87895e5c301cde7 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -326,12 +326,6 @@ export default {
     workItemWeight() {
       return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
     },
-    showRolledUpWeight() {
-      return this.workItemWeight?.widgetDefinition?.rollUp;
-    },
-    rolledUpWeight() {
-      return this.workItemWeight?.rolledUpWeight;
-    },
     workItemBodyClass() {
       return {
         'gl-pt-5': !this.updateError && !this.isModal,
@@ -729,8 +723,6 @@ export default {
               :work-item-iid="workItemIid"
               :can-update="canUpdate"
               :can-update-children="canUpdateChildren"
-              :rolled-up-weight="rolledUpWeight"
-              :show-rolled-up-weight="showRolledUpWeight"
               :confidential="workItem.confidential"
               :allowed-child-types="allowedChildTypes"
               @show-modal="openInModal"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ba2bdb73899b545b50a02837861484e0dc6c1fcc
--- /dev/null
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_rolled_up_data.vue
@@ -0,0 +1,125 @@
+<script>
+import { GlIcon, GlTooltip, GlPopover } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+import { findWidget } from '~/issues/list/utils';
+import { i18n, WIDGET_TYPE_WEIGHT } from '../../constants';
+
+export default {
+  components: {
+    GlIcon,
+    GlTooltip,
+    GlPopover,
+  },
+  i18n: {
+    progressLabel: s__('WorkItem|Progress'),
+    weightCompletedLabel: s__('WorkItem|issue weight completed'),
+  },
+  props: {
+    fullPath: {
+      type: String,
+      required: true,
+    },
+    workItemType: {
+      type: String,
+      required: true,
+    },
+    workItemId: {
+      type: String,
+      required: true,
+    },
+    workItemIid: {
+      type: String,
+      required: false,
+      default: null,
+    },
+  },
+  apollo: {
+    workItem: {
+      query: workItemByIidQuery,
+      variables() {
+        return {
+          fullPath: this.fullPath,
+          iid: this.workItemIid,
+        };
+      },
+      update(data) {
+        return data.workspace?.workItem || {};
+      },
+      skip() {
+        return !this.workItemIid;
+      },
+      error(e) {
+        this.$emit('error', i18n.fetchError);
+        this.error = e.message || i18n.fetchError;
+      },
+    },
+  },
+  computed: {
+    workItemWeight() {
+      return findWidget(WIDGET_TYPE_WEIGHT, this.workItem);
+    },
+    shouldRolledUpWeightBeVisible() {
+      return this.showRolledUpWeight && this.rolledUpWeight !== null;
+    },
+    showRolledUpProgress() {
+      return this.rolledUpWeight && this.rolledUpCompletedWeight !== null;
+    },
+    showRolledUpWeight() {
+      return this.workItemWeight?.widgetDefinition?.rollUp;
+    },
+    rolledUpWeight() {
+      return this.workItemWeight?.rolledUpWeight;
+    },
+    rolledUpCompletedWeight() {
+      return this.workItemWeight?.rolledUpCompletedWeight;
+    },
+    completedWeightPercentage() {
+      return Math.round((this.rolledUpCompletedWeight / this.rolledUpWeight) * 100);
+    },
+  },
+};
+</script>
+
+<template>
+  <div class="gl-flex">
+    <!-- Rolled up weight -->
+    <span
+      v-if="shouldRolledUpWeightBeVisible"
+      ref="weightData"
+      tabindex="0"
+      data-testid="work-item-rollup-weight"
+      class="gl-ml-3 gl-flex gl-cursor-help gl-items-center gl-gap-2 gl-font-normal gl-text-secondary"
+    >
+      <gl-icon name="weight" class="gl-text-secondary" />
+      <span data-testid="work-item-weight-value" class="gl-text-sm">{{ rolledUpWeight }}</span>
+      <gl-tooltip :target="() => $refs.weightData">
+        <span class="gl-font-bold">
+          {{ __('Weight') }}
+        </span>
+      </gl-tooltip>
+    </span>
+    <!--- END Rolled up weight -->
+
+    <!-- Rolled up Progress -->
+    <span
+      v-if="showRolledUpProgress"
+      ref="progressBadge"
+      tabindex="0"
+      data-testid="work-item-rollup-progress"
+      class="gl-ml-3 gl-flex gl-items-center gl-gap-2 gl-font-normal gl-text-secondary"
+    >
+      <gl-icon name="progress" class="gl-text-secondary" />
+      <span data-testid="work-item-progress-value" class="gl-text-sm"
+        >{{ completedWeightPercentage }}%</span
+      >
+
+      <gl-popover triggers="hover focus" :target="() => $refs.progressBadge">
+        <template #title>{{ $options.i18n.progressLabel }}</template>
+        <span class="gl-font-bold">{{ rolledUpCompletedWeight }}/{{ rolledUpWeight }}</span>
+        {{ $options.i18n.weightCompletedLabel }}
+      </gl-popover>
+    </span>
+    <!-- END Rolled up Progress -->
+  </div>
+</template>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
index ffa5962f100f5693c7c3cc57dd4401df3ba43fd5..72bc91de4795b6c0c37477f78ac57c8e567152e4 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree.vue
@@ -1,5 +1,5 @@
 <script>
-import { GlAlert, GlIcon, GlTooltip } from '@gitlab/ui';
+import { GlAlert } from '@gitlab/ui';
 import { sprintf, s__ } from '~/locale';
 import { createAlert } from '~/alert';
 import CrudComponent from '~/vue_shared/components/crud_component.vue';
@@ -20,6 +20,7 @@ import WorkItemMoreActions from '../shared/work_item_more_actions.vue';
 import WorkItemActionsSplitButton from './work_item_actions_split_button.vue';
 import WorkItemLinksForm from './work_item_links_form.vue';
 import WorkItemChildrenWrapper from './work_item_children_wrapper.vue';
+import WorkItemRolledUpData from './work_item_rolled_up_data.vue';
 
 export default {
   FORM_TYPES,
@@ -28,14 +29,13 @@ export default {
   WORK_ITEM_TYPE_ENUM_KEY_RESULT,
   components: {
     GlAlert,
-    GlIcon,
-    GlTooltip,
     WorkItemActionsSplitButton,
     CrudComponent,
     WorkItemLinksForm,
     WorkItemChildrenWrapper,
     WorkItemChildrenLoadMore,
     WorkItemMoreActions,
+    WorkItemRolledUpData,
   },
   inject: ['hasSubepicsFeature'],
   props: {
@@ -86,16 +86,6 @@ export default {
       required: false,
       default: () => [],
     },
-    showRolledUpWeight: {
-      type: Boolean,
-      required: false,
-      default: false,
-    },
-    rolledUpWeight: {
-      type: Number,
-      required: false,
-      default: 0,
-    },
   },
   data() {
     return {
@@ -256,20 +246,12 @@ export default {
     data-testid="work-item-tree"
   >
     <template #count>
-      <span
-        v-if="shouldRolledUpWeightBeVisible"
-        ref="weightData"
-        data-testid="rollup-weight"
-        class="gl-ml-3 gl-flex gl-cursor-help gl-items-center gl-gap-2 gl-font-normal gl-text-secondary"
-      >
-        <gl-icon name="weight" class="gl-text-subtle" />
-        <span data-testid="weight-value" class="gl-text-sm">{{ rolledUpWeight }}</span>
-        <gl-tooltip :target="() => $refs.weightData">
-          <span class="gl-font-bold">
-            {{ __('Weight') }}
-          </span>
-        </gl-tooltip>
-      </span>
+      <work-item-rolled-up-data
+        :work-item-id="workItemId"
+        :work-item-iid="workItemIid"
+        :work-item-type="workItemType"
+        :full-path="fullPath"
+      />
     </template>
 
     <template #actions>
diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js
index 44f9fbb58899baff75c0944da5e268f192a8c208..31cd9e1b052e33a9d51fd4e637011dc15092473c 100644
--- a/app/assets/javascripts/work_items/graphql/cache_utils.js
+++ b/app/assets/javascripts/work_items/graphql/cache_utils.js
@@ -380,6 +380,7 @@ export const setNewWorkItemCache = async (
           type: 'WEIGHT',
           weight: null,
           rolledUpWeight: 0,
+          rolledUpCompletedWeight: 0,
           widgetDefinition: {
             editable: weightWidgetData?.editable,
             rollUp: weightWidgetData?.rollUp,
diff --git a/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
index 7420f6de8ae0478a76bf437ddb5a8e17bc34df99..31c1c34d388229336d97c31027edeac4f960842d 100644
--- a/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
+++ b/ee/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql
@@ -53,6 +53,7 @@ fragment WorkItemWidgets on WorkItemWidget {
   ... on WorkItemWidgetWeight {
     weight
     rolledUpWeight
+    rolledUpCompletedWeight
     widgetDefinition {
       editable
       rollUp
diff --git a/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js b/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..588b6553f31f45c4ec4b4b85f1773a2e565617cb
--- /dev/null
+++ b/ee/spec/frontend/work_items/components/work_item_links/work_item_rolled_up_data_spec.js
@@ -0,0 +1,124 @@
+import Vue from 'vue';
+import { GlIcon } from '@gitlab/ui';
+import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import WorkItemRolledUpData from '~/work_items/components/work_item_links/work_item_rolled_up_data.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
+import { workItemByIidResponseFactory } from 'jest/work_items/mock_data';
+
+Vue.use(VueApollo);
+
+describe('WorkItemRollUpData', () => {
+  let wrapper;
+
+  const findRolledUpWeight = () => wrapper.findByTestId('work-item-rollup-weight');
+  const findRolledUpWeightValue = () => wrapper.findByTestId('work-item-weight-value');
+  const findRolledUpProgress = () => wrapper.findByTestId('work-item-rollup-progress');
+  const findRolledUpProgressValue = () => wrapper.findByTestId('work-item-progress-value');
+
+  const workItemQueryResponse = workItemByIidResponseFactory({
+    canUpdate: true,
+    canDelete: true,
+  });
+  const workItemSuccessQueryHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
+
+  const createComponent = ({
+    workItemType = 'Objective',
+    workItemIid = '2',
+    workItemQueryHandler = workItemSuccessQueryHandler,
+  } = {}) => {
+    wrapper = shallowMountExtended(WorkItemRolledUpData, {
+      propsData: {
+        fullPath: 'test/project',
+        workItemType,
+        workItemIid,
+        workItemId: 'gid://gitlab/WorkItem/2',
+      },
+      apolloProvider: createMockApollo([[workItemByIidQuery, workItemQueryHandler]]),
+    });
+  };
+
+  describe('rolled up weight', () => {
+    it.each`
+      isRollUp | rolledUpWeight | rollUpWeightVisible | expected
+      ${false} | ${0}           | ${false}            | ${'rollup weight is not displayed'}
+      ${false} | ${10}          | ${false}            | ${'rollup weight is not displayed'}
+      ${true}  | ${0}           | ${true}             | ${'rollup weight is displayed'}
+      ${true}  | ${null}        | ${false}            | ${'rollup weight is not displayed'}
+      ${true}  | ${10}          | ${true}             | ${'rollup weight is displayed'}
+    `(
+      'When the roll up weight is $isRollUp and rolledUpWeight is $rolledUpWeight, $expected',
+      async ({ isRollUp, rollUpWeightVisible, rolledUpWeight }) => {
+        const workItemResponse = workItemByIidResponseFactory({
+          canUpdate: true,
+          rolledUpWeight,
+          editableWeightWidget: !isRollUp,
+        });
+        createComponent({
+          workItemQueryHandler: jest.fn().mockResolvedValue(workItemResponse),
+        });
+
+        await waitForPromises();
+
+        expect(findRolledUpWeight().exists()).toBe(rollUpWeightVisible);
+      },
+    );
+
+    it('should show the correct value when rolledUpWeight is visible', async () => {
+      const workItemResponse = workItemByIidResponseFactory({
+        canUpdate: true,
+        rolledUpWeight: 10,
+        editableWeightWidget: false,
+      });
+      createComponent({ workItemQueryHandler: jest.fn().mockResolvedValue(workItemResponse) });
+
+      await waitForPromises();
+
+      expect(findRolledUpWeight().exists()).toBe(true);
+      expect(findRolledUpWeight().findComponent(GlIcon).props('name')).toBe('weight');
+      expect(findRolledUpWeightValue().text()).toBe('10');
+    });
+  });
+
+  describe('rolled up progress', () => {
+    it('should not show the rolled up progress when rolled up weight is null', async () => {
+      const workItemResponse = workItemByIidResponseFactory({
+        canUpdate: true,
+        rolledUpWeight: null,
+        editableWeightWidget: false,
+      });
+      createComponent({ workItemQueryHandler: jest.fn().mockResolvedValue(workItemResponse) });
+
+      await waitForPromises();
+
+      expect(findRolledUpProgress().exists()).toBe(false);
+      expect(findRolledUpProgressValue().exists()).toBe(false);
+    });
+
+    it('should show the correct value when rolledUpWeight and rolledUpCompletedWeight exist', async () => {
+      const workItemResponse = workItemByIidResponseFactory({
+        canUpdate: true,
+        rolledUpWeight: 12,
+        rolledUpCompletedWeight: 5,
+        editableWeightWidget: false,
+      });
+      createComponent({ workItemQueryHandler: jest.fn().mockResolvedValue(workItemResponse) });
+
+      await waitForPromises();
+
+      expect(findRolledUpProgress().exists()).toBe(true);
+      expect(findRolledUpProgress().findComponent(GlIcon).props('name')).toBe('progress');
+      expect(findRolledUpProgressValue().text()).toBe('42%');
+    });
+  });
+
+  it('when the query is not successful , and error is emitted', async () => {
+    const errorHandler = jest.fn().mockRejectedValue('Oops');
+    createComponent({ workItemQueryHandler: errorHandler });
+    await waitForPromises();
+
+    expect(wrapper.emitted('error')).toHaveLength(1);
+  });
+});
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 754f9daa8336b79c5070e447549878f39a50a7a4..6027d1b9e1cc24d7d6e0d4b9d77e71cf5ba704fa 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -61444,6 +61444,9 @@ msgstr ""
 msgid "WorkItem|Pink"
 msgstr ""
 
+msgid "WorkItem|Progress"
+msgstr ""
+
 msgid "WorkItem|Promoted to objective."
 msgstr ""
 
@@ -61660,6 +61663,9 @@ msgstr ""
 msgid "WorkItem|is blocked by"
 msgstr ""
 
+msgid "WorkItem|issue weight completed"
+msgstr ""
+
 msgid "WorkItem|item"
 msgstr ""
 
diff --git a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
index 5438446c496008a0f286564f5ec298bd90e8dcc5..e560812855d6f422a03d5288e8ef20457d6a2d3e 100644
--- a/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
+++ b/spec/frontend/work_items/components/work_item_links/work_item_tree_spec.js
@@ -1,6 +1,6 @@
 import Vue, { nextTick } from 'vue';
 import VueApollo from 'vue-apollo';
-import { GlAlert, GlIcon, GlLoadingIcon } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
 import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
 import createMockApollo from 'helpers/mock_apollo_helper';
 import waitForPromises from 'helpers/wait_for_promises';
@@ -46,9 +46,6 @@ describe('WorkItemTree', () => {
   const findErrorMessage = () => wrapper.findComponent(GlAlert);
   const findWorkItemLinkChildrenWrapper = () => wrapper.findComponent(WorkItemChildrenWrapper);
   const findMoreActions = () => wrapper.findComponent(WorkItemMoreActions);
-  const findRolledUpWeight = () => wrapper.findByTestId('rollup-weight');
-  const findRolledUpWeightValue = () => wrapper.findByTestId('weight-value');
-  const findCrudComponent = () => wrapper.findComponent(CrudComponent);
 
   const createComponent = async ({
     workItemType = 'Objective',
@@ -59,8 +56,6 @@ describe('WorkItemTree', () => {
     canUpdateChildren = true,
     hasSubepicsFeature = true,
     workItemHierarchyTreeHandler = workItemHierarchyTreeResponseHandler,
-    showRolledUpWeight = false,
-    rolledUpWeight = 0,
   } = {}) => {
     wrapper = shallowMountExtended(WorkItemTree, {
       propsData: {
@@ -72,8 +67,6 @@ describe('WorkItemTree', () => {
         confidential,
         canUpdate,
         canUpdateChildren,
-        showRolledUpWeight,
-        rolledUpWeight,
       },
       apolloProvider: createMockApollo([[getWorkItemTreeQuery, workItemHierarchyTreeHandler]]),
       provide: {
@@ -302,39 +295,4 @@ describe('WorkItemTree', () => {
       expect(findWorkItemLinkChildrenWrapper().props('showLabels')).toBe(true);
     });
   });
-
-  describe('rollup data', () => {
-    describe('rolledUp weight', () => {
-      it.each`
-        showRolledUpWeight | rolledUpWeight | rollUpWeightVisible | expected
-        ${false}           | ${0}           | ${false}            | ${'rollup weight is not displayed'}
-        ${false}           | ${10}          | ${false}            | ${'rollup weight is not displayed'}
-        ${true}            | ${0}           | ${true}             | ${'rollup weight is displayed'}
-        ${true}            | ${null}        | ${false}            | ${'rollup weight is not displayed'}
-        ${true}            | ${10}          | ${true}             | ${'rollup weight is displayed'}
-      `(
-        'When showRolledUpWeight is $showRolledUpWeight and rolledUpWeight is $rolledUpWeight, $expected',
-        ({ showRolledUpWeight, rollUpWeightVisible, rolledUpWeight }) => {
-          createComponent({ showRolledUpWeight, rolledUpWeight });
-
-          expect(findRolledUpWeight().exists()).toBe(rollUpWeightVisible);
-        },
-      );
-
-      it('should show the correct value when rolledUpWeight is visible', async () => {
-        await createComponent({ showRolledUpWeight: true, rolledUpWeight: 10 });
-
-        expect(findRolledUpWeight().exists()).toBe(true);
-        expect(findRolledUpWeight().findComponent(GlIcon).props('name')).toBe('weight');
-        expect(findRolledUpWeightValue().text()).toBe('10');
-      });
-    });
-  });
-
-  it('renders children count', async () => {
-    await createComponent({ showRolledUpWeight: true });
-
-    expect(findCrudComponent().props('icon')).toBe('issue-type-task');
-    expect(findCrudComponent().props('count')).toBe(1);
-  });
 });
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index d8bdab12f0e90dfda54916b17fbde3c794c94da8..208477a876bb62f52734dfddbbc7d473c292f8a1 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -1069,6 +1069,8 @@ export const workItemResponseFactory = ({
   editableWeightWidget = true,
   hasParent = false,
   healthStatus = 'onTrack',
+  rolledUpWeight = 0,
+  rolledUpCompletedWeight = 0,
 } = {}) => ({
   data: {
     workItem: {
@@ -1161,7 +1163,8 @@ export const workItemResponseFactory = ({
           ? {
               type: 'WEIGHT',
               weight: null,
-              rolledUpWeight: 0,
+              rolledUpWeight,
+              rolledUpCompletedWeight,
               widgetDefinition: {
                 editable: editableWeightWidget,
                 rollUp: !editableWeightWidget,