From 8cf4e1496ac11e4df12491dbd75e0bfaaee5f7e6 Mon Sep 17 00:00:00 2001
From: Deepika Guliani <dguliani@gitlab.com>
Date: Mon, 10 Mar 2025 19:13:18 +0530
Subject: [PATCH] Fix WorkItemType for reference work items

Changelog: fixed
---
 .../components/work_item_parent.vue           |  64 ++++---
 .../graphql/project_work_items.query.graphql  |  10 +
 .../work_items/graphql/resolvers.js           | 179 +++++++++---------
 .../work_items_by_references.query.graphql    |   5 +
 .../attach_existing_issue_spec.js             |  21 +-
 .../shared/work_item_token_input_spec.js      |  48 +++++
 spec/frontend/work_items/mock_data.js         |  48 +++++
 7 files changed, 257 insertions(+), 118 deletions(-)

diff --git a/app/assets/javascripts/work_items/components/work_item_parent.vue b/app/assets/javascripts/work_items/components/work_item_parent.vue
index 676c6591798b6..cfab8ea34d0ff 100644
--- a/app/assets/javascripts/work_items/components/work_item_parent.vue
+++ b/app/assets/javascripts/work_items/components/work_item_parent.vue
@@ -204,41 +204,45 @@ export default {
       this.searchStarted = true;
     },
     async updateParent() {
-      if (this.parent?.id === this.localSelectedItem) return;
+      try {
+        if (this.parent?.id === this.localSelectedItem) return;
 
-      this.updateInProgress = true;
+        this.updateInProgress = true;
 
-      if (this.workItemId === newWorkItemId(this.workItemType)) {
-        const visibleWorkItems = this.workItemsByReference.concat(this.workspaceWorkItems);
+        if (this.workItemId === newWorkItemId(this.workItemType)) {
+          const visibleWorkItems = this.workItemsByReference.concat(this.workspaceWorkItems);
 
-        this.$apollo
-          .mutate({
-            mutation: updateNewWorkItemMutation,
-            variables: {
-              input: {
-                fullPath: this.fullPath,
-                parent:
-                  this.localSelectedItem && visibleWorkItems.length
-                    ? {
-                        ...visibleWorkItems?.find(({ id }) => id === this.localSelectedItem),
-                        webUrl: this.parentWebUrl ?? null,
-                      }
-                    : null,
-                workItemType: this.workItemType,
+          this.$apollo
+            .mutate({
+              mutation: updateNewWorkItemMutation,
+              variables: {
+                input: {
+                  fullPath: this.fullPath,
+                  parent:
+                    this.localSelectedItem && visibleWorkItems.length
+                      ? {
+                          ...visibleWorkItems?.find(({ id }) => id === this.localSelectedItem),
+                          webUrl: this.parentWebUrl ?? null,
+                        }
+                      : null,
+                  workItemType: this.workItemType,
+                },
               },
-            },
-          })
-          .catch((error) => {
-            Sentry.captureException(error);
-          })
-          .finally(() => {
-            this.searchStarted = false;
-            this.updateInProgress = false;
-          });
-        return;
-      }
+            })
+            .catch((error) => {
+              this.$emit(
+                'error',
+                sprintfWorkItem(I18N_WORK_ITEM_ERROR_UPDATING, this.workItemType),
+              );
+              Sentry.captureException(error);
+            })
+            .finally(() => {
+              this.searchStarted = false;
+              this.updateInProgress = false;
+            });
+          return;
+        }
 
-      try {
         const {
           data: {
             workItemUpdate: { errors },
diff --git a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
index 17b338f7a8d98..8010387f2eff2 100644
--- a/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/project_work_items.query.graphql
@@ -15,6 +15,11 @@ query projectWorkItems(
         iid
         title
         confidential
+        workItemType {
+          id
+          name
+          iconName
+        }
       }
     }
     workItemsByIid: workItems(iid: $iid, types: $types) @include(if: $searchByIid) {
@@ -23,6 +28,11 @@ query projectWorkItems(
         iid
         title
         confidential
+        workItemType {
+          id
+          name
+          iconName
+        }
       }
     }
   }
diff --git a/app/assets/javascripts/work_items/graphql/resolvers.js b/app/assets/javascripts/work_items/graphql/resolvers.js
index 347c2a4d1fd49..8246fb12ab846 100644
--- a/app/assets/javascripts/work_items/graphql/resolvers.js
+++ b/app/assets/javascripts/work_items/graphql/resolvers.js
@@ -1,5 +1,6 @@
 import { set, isEmpty } from 'lodash';
 import { produce } from 'immer';
+import * as Sentry from '~/sentry/sentry_browser_wrapper';
 import { findWidget } from '~/issues/list/utils';
 import { newDate, toISODateFormat } from '~/lib/utils/datetime_utility';
 import { updateDraft } from '~/lib/utils/autosave';
@@ -68,93 +69,97 @@ export const updateNewWorkItemCache = (input, cache) => {
     parent,
   } = input;
 
-  const query = workItemByIidQuery;
-  const variables = {
-    fullPath: newWorkItemFullPath(fullPath, workItemType),
-    iid: NEW_WORK_ITEM_IID,
-  };
-
-  cache.updateQuery({ query, variables }, (sourceData) =>
-    produce(sourceData, (draftData) => {
-      const widgetUpdates = [
-        {
-          widgetType: WIDGET_TYPE_ASSIGNEES,
-          newData: assignees,
-          nodePath: 'assignees.nodes',
-        },
-        {
-          widgetType: WIDGET_TYPE_LABELS,
-          newData: labels,
-          nodePath: 'labels.nodes',
-        },
-        {
-          widgetType: WIDGET_TYPE_COLOR,
-          newData: color,
-          nodePath: 'color',
-        },
-        {
-          widgetType: WIDGET_TYPE_CRM_CONTACTS,
-          newData: crmContacts,
-          nodePath: 'contacts.nodes',
-        },
-        {
-          widgetType: WIDGET_TYPE_DESCRIPTION,
-          newData: description,
-          nodePath: 'description',
-        },
-        {
-          widgetType: WIDGET_TYPE_HEALTH_STATUS,
-          newData: healthStatus,
-          nodePath: 'healthStatus',
-        },
-        {
-          widgetType: WIDGET_TYPE_ITERATION,
-          newData: iteration,
-          nodePath: 'iteration',
-        },
-        {
-          widgetType: WIDGET_TYPE_WEIGHT,
-          newData: weight,
-          nodePath: 'weight',
-        },
-        {
-          widgetType: WIDGET_TYPE_MILESTONE,
-          newData: milestone,
-          nodePath: 'milestone',
-        },
-        {
-          widgetType: WIDGET_TYPE_HIERARCHY,
-          newData: parent,
-          nodePath: 'parent',
-        },
-      ];
-
-      widgetUpdates.forEach(({ widgetType, newData, nodePath }) => {
-        updateWidget(draftData, widgetType, newData, nodePath);
-      });
-
-      updateDatesWidget(draftData, rolledUpDates);
-
-      // We want to allow users to delete a title for an in-progress work item draft
-      // as we check for the title being valid when submitting the form
-      if (title !== undefined) draftData.workspace.workItem.title = title;
-
-      if (confidential !== undefined) draftData.workspace.workItem.confidential = confidential;
-    }),
-  );
-
-  const newData = cache.readQuery({ query, variables });
-
-  const autosaveKey = getNewWorkItemAutoSaveKey(fullPath, workItemType);
-
-  const isQueryDataValid = !isEmpty(newData) && newData?.workspace?.workItem;
-
-  const isWorkItemToResolveDiscussion = getParameterByName(
-    'merge_request_to_resolve_discussions_of',
-  );
-
-  if (isQueryDataValid && autosaveKey && !isWorkItemToResolveDiscussion) {
-    updateDraft(autosaveKey, JSON.stringify(newData));
+  try {
+    const query = workItemByIidQuery;
+    const variables = {
+      fullPath: newWorkItemFullPath(fullPath, workItemType),
+      iid: NEW_WORK_ITEM_IID,
+    };
+
+    cache.updateQuery({ query, variables }, (sourceData) =>
+      produce(sourceData, (draftData) => {
+        const widgetUpdates = [
+          {
+            widgetType: WIDGET_TYPE_ASSIGNEES,
+            newData: assignees,
+            nodePath: 'assignees.nodes',
+          },
+          {
+            widgetType: WIDGET_TYPE_LABELS,
+            newData: labels,
+            nodePath: 'labels.nodes',
+          },
+          {
+            widgetType: WIDGET_TYPE_COLOR,
+            newData: color,
+            nodePath: 'color',
+          },
+          {
+            widgetType: WIDGET_TYPE_CRM_CONTACTS,
+            newData: crmContacts,
+            nodePath: 'contacts.nodes',
+          },
+          {
+            widgetType: WIDGET_TYPE_DESCRIPTION,
+            newData: description,
+            nodePath: 'description',
+          },
+          {
+            widgetType: WIDGET_TYPE_HEALTH_STATUS,
+            newData: healthStatus,
+            nodePath: 'healthStatus',
+          },
+          {
+            widgetType: WIDGET_TYPE_ITERATION,
+            newData: iteration,
+            nodePath: 'iteration',
+          },
+          {
+            widgetType: WIDGET_TYPE_WEIGHT,
+            newData: weight,
+            nodePath: 'weight',
+          },
+          {
+            widgetType: WIDGET_TYPE_MILESTONE,
+            newData: milestone,
+            nodePath: 'milestone',
+          },
+          {
+            widgetType: WIDGET_TYPE_HIERARCHY,
+            newData: parent,
+            nodePath: 'parent',
+          },
+        ];
+
+        widgetUpdates.forEach(({ widgetType, newData, nodePath }) => {
+          updateWidget(draftData, widgetType, newData, nodePath);
+        });
+
+        updateDatesWidget(draftData, rolledUpDates);
+
+        // We want to allow users to delete a title for an in-progress work item draft
+        // as we check for the title being valid when submitting the form
+        if (title !== undefined) draftData.workspace.workItem.title = title;
+
+        if (confidential !== undefined) draftData.workspace.workItem.confidential = confidential;
+      }),
+    );
+
+    const newData = cache.readQuery({ query, variables });
+
+    const autosaveKey = getNewWorkItemAutoSaveKey(fullPath, workItemType);
+
+    const isQueryDataValid = !isEmpty(newData) && newData?.workspace?.workItem;
+
+    const isWorkItemToResolveDiscussion = getParameterByName(
+      'merge_request_to_resolve_discussions_of',
+    );
+
+    if (isQueryDataValid && autosaveKey && !isWorkItemToResolveDiscussion) {
+      updateDraft(autosaveKey, JSON.stringify(newData));
+    }
+  } catch (e) {
+    Sentry.captureException(e);
   }
 };
 
diff --git a/app/assets/javascripts/work_items/graphql/work_items_by_references.query.graphql b/app/assets/javascripts/work_items/graphql/work_items_by_references.query.graphql
index 1e8d62596b747..1cb061205fd61 100644
--- a/app/assets/javascripts/work_items/graphql/work_items_by_references.query.graphql
+++ b/app/assets/javascripts/work_items/graphql/work_items_by_references.query.graphql
@@ -5,6 +5,11 @@ query getWorkItemsByReferences($contextNamespacePath: ID!, $refs: [String!]!) {
       iid
       title
       confidential
+      workItemType {
+        id
+        name
+        iconName
+      }
     }
   }
 }
diff --git a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/attach_existing_issue_spec.js b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/attach_existing_issue_spec.js
index 1b5255be0951c..755166b79c3fa 100644
--- a/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/attach_existing_issue_spec.js
+++ b/ee/spec/frontend/security_dashboard/components/shared/vulnerability_report/attach_existing_issue_spec.js
@@ -24,8 +24,27 @@ const defaultWorkItemsResponse = {
           iid: '1',
           title: 'Item 1',
           confidential: false,
+          workItemType: {
+            id: 'gid://gitlab/WorkItems::Type/5',
+            name: 'Task',
+            iconName: 'issue-type-task',
+            __typename: 'WorkItemType',
+          },
+          __typename: 'WorkItem',
+        },
+        {
+          id: 'gid://gitlab/WorkItem/2',
+          iid: '2',
+          title: 'Item 2',
+          confidential: false,
+          workItemType: {
+            id: 'gid://gitlab/WorkItems::Type/5',
+            name: 'Task',
+            iconName: 'issue-type-task',
+            __typename: 'WorkItemType',
+          },
+          __typename: 'WorkItem',
         },
-        { id: 'gid://gitlab/WorkItem/2', iid: '2', title: 'Item 2', confidential: false },
       ],
     },
   },
diff --git a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js
index df8c5c2e367c4..7860ec86ea44b 100644
--- a/spec/frontend/work_items/components/shared/work_item_token_input_spec.js
+++ b/spec/frontend/work_items/components/shared/work_item_token_input_spec.js
@@ -48,6 +48,12 @@ describe('WorkItemTokenInput', () => {
           iid: '2',
           title: 'Task 1',
           confidential: false,
+          workItemType: {
+            id: 'gid://gitlab/WorkItems::Type/5',
+            name: 'Task',
+            iconName: 'issue-type-task',
+            __typename: 'WorkItemType',
+          },
           __typename: 'WorkItem',
         },
         {
@@ -55,6 +61,12 @@ describe('WorkItemTokenInput', () => {
           iid: '3',
           title: 'Task 2',
           confidential: false,
+          workItemType: {
+            id: 'gid://gitlab/WorkItems::Type/5',
+            name: 'Task',
+            iconName: 'issue-type-task',
+            __typename: 'WorkItemType',
+          },
           __typename: 'WorkItem',
         },
         {
@@ -62,6 +74,12 @@ describe('WorkItemTokenInput', () => {
           iid: '4',
           title: 'Task 3',
           confidential: false,
+          workItemType: {
+            id: 'gid://gitlab/WorkItems::Type/5',
+            name: 'Task',
+            iconName: 'issue-type-task',
+            __typename: 'WorkItemType',
+          },
           __typename: 'WorkItem',
         },
       ],
@@ -157,6 +175,12 @@ describe('WorkItemTokenInput', () => {
     iid: '3',
     title: 'Task 2',
     confidential: false,
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/5',
+      name: 'Task',
+      iconName: 'issue-type-task',
+      __typename: 'WorkItemType',
+    },
     __typename: 'WorkItem',
   };
 
@@ -165,6 +189,12 @@ describe('WorkItemTokenInput', () => {
     iid: 'Task 2 <svg><use href=#/></svg>',
     title: 'Task 2 <svg><use href=#/></svg>',
     confidential: false,
+    workItemType: {
+      id: 'gid://gitlab/WorkItems::Type/5',
+      name: 'Task',
+      iconName: 'issue-type-task',
+      __typename: 'WorkItemType',
+    },
     __typename: 'WorkItem',
   };
 
@@ -298,6 +328,12 @@ describe('WorkItemTokenInput', () => {
       iid: '101',
       title: 'Task 3',
       confidential: false,
+      workItemType: {
+        id: 'gid://gitlab/WorkItems::Type/5',
+        name: 'Task',
+        iconName: 'issue-type-task',
+        __typename: 'WorkItemType',
+      },
       __typename: 'WorkItem',
     };
     const mockWorkItemResponseItem2 = {
@@ -305,6 +341,12 @@ describe('WorkItemTokenInput', () => {
       iid: '3',
       title: 'Task 123',
       confidential: false,
+      workItemType: {
+        id: 'gid://gitlab/WorkItems::Type/5',
+        name: 'Task',
+        iconName: 'issue-type-task',
+        __typename: 'WorkItemType',
+      },
       __typename: 'WorkItem',
     };
     const mockWorkItemResponseItem3 = {
@@ -312,6 +354,12 @@ describe('WorkItemTokenInput', () => {
       iid: '123',
       title: 'Task 2',
       confidential: false,
+      workItemType: {
+        id: 'gid://gitlab/WorkItems::Type/5',
+        name: 'Task',
+        iconName: 'issue-type-task',
+        __typename: 'WorkItemType',
+      },
       __typename: 'WorkItem',
     };
 
diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js
index 3ed5ebc70ceee..0781af2c3ff62 100644
--- a/spec/frontend/work_items/mock_data.js
+++ b/spec/frontend/work_items/mock_data.js
@@ -429,6 +429,12 @@ export const mockworkItemReferenceQueryResponse = {
           iid: '111',
           title: 'Objective linked items 104',
           confidential: false,
+          workItemType: {
+            iconName: 'issue-type-objective',
+            id: 'gid://gitlab/WorkItems::Type/6',
+            name: 'Objective',
+            __typename: 'WorkItemType',
+          },
           __typename: 'WorkItem',
         },
       ],
@@ -3232,6 +3238,12 @@ export const availableWorkItemsResponse = {
             iid: '2',
             title: 'Task 1',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-task',
+              id: 'gid://gitlab/WorkItems::Type/5',
+              name: 'Task',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
           {
@@ -3239,6 +3251,12 @@ export const availableWorkItemsResponse = {
             iid: '3',
             title: 'Task 2',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-task',
+              id: 'gid://gitlab/WorkItems::Type/5',
+              name: 'Task',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
           {
@@ -3246,6 +3264,12 @@ export const availableWorkItemsResponse = {
             iid: '4',
             title: 'Task 3',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-task',
+              id: 'gid://gitlab/WorkItems::Type/5',
+              name: 'Task',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
         ],
@@ -3266,6 +3290,12 @@ export const availableObjectivesResponse = {
             iid: '122',
             title: 'Objective 101',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-objective',
+              id: 'gid://gitlab/WorkItems::Type/6',
+              name: 'Objective',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
           {
@@ -3273,6 +3303,12 @@ export const availableObjectivesResponse = {
             iid: '118',
             title: 'Objective 103',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-objective',
+              id: 'gid://gitlab/WorkItems::Type/6',
+              name: 'Objective',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
           {
@@ -3280,6 +3316,12 @@ export const availableObjectivesResponse = {
             iid: '117',
             title: 'Objective 102',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-objective',
+              id: 'gid://gitlab/WorkItems::Type/6',
+              name: 'Objective',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
         ],
@@ -3300,6 +3342,12 @@ export const searchedObjectiveResponse = {
             iid: '122',
             title: 'Objective 101',
             confidential: false,
+            workItemType: {
+              iconName: 'issue-type-objective',
+              id: 'gid://gitlab/WorkItems::Type/6',
+              name: 'Objective',
+              __typename: 'WorkItemType',
+            },
             __typename: 'WorkItem',
           },
         ],
-- 
GitLab