diff --git a/app/assets/javascripts/issues/show/components/edited.vue b/app/assets/javascripts/issues/show/components/edited.vue index e2f3b5cbe108e91e6acab5a5661d7ee73fdc74d1..ad6bdea4ccf91bbc82597b864360dd212100759f 100644 --- a/app/assets/javascripts/issues/show/components/edited.vue +++ b/app/assets/javascripts/issues/show/components/edited.vue @@ -34,7 +34,9 @@ export default { }, computed: { completedCount() { - return this.taskCompletionStatus.completed_count; + // The new Work Item GraphQL endpoint returns `completedCount` instead, so added a fallback + // to it for the work item view + return this.taskCompletionStatus.completed_count ?? this.taskCompletionStatus.completedCount; }, count() { return this.taskCompletionStatus.count; diff --git a/app/assets/javascripts/work_items/components/work_item_description.vue b/app/assets/javascripts/work_items/components/work_item_description.vue index 086d0a67d90b778155acdb5267f035a8afe7c322..506ad72ada0a128fe8621d50f52420f55c3ec833 100644 --- a/app/assets/javascripts/work_items/components/work_item_description.vue +++ b/app/assets/javascripts/work_items/components/work_item_description.vue @@ -155,6 +155,9 @@ export default { workItemType() { return this.workItem?.workItemType?.name; }, + taskCompletionStatus() { + return this.workItemDescription?.taskCompletionStatus; + }, lastEditedAt() { return this.workItemDescription?.lastEditedAt; }, @@ -188,6 +191,9 @@ export default { 'gl-mb-5 common-note-form': true, }; }, + showEditedAt() { + return (this.taskCompletionStatus || this.lastEditedAt) && !this.editMode; + }, }, watch: { updateInProgress(newValue) { @@ -353,7 +359,8 @@ export default { @descriptionUpdated="handleDescriptionTextUpdated" /> <edited-at - v-if="lastEditedAt && !editMode" + v-if="showEditedAt" + :task-completion-status="taskCompletionStatus" :updated-at="lastEditedAt" :updated-by-name="lastEditedByName" :updated-by-path="lastEditedByPath" diff --git a/app/assets/javascripts/work_items/graphql/cache_utils.js b/app/assets/javascripts/work_items/graphql/cache_utils.js index 7689eb3f2b7a5ce0593a47cb5750d53a8fd81e8d..82841a6d1cd7376c9da3861cfb844b5205ebb36f 100644 --- a/app/assets/javascripts/work_items/graphql/cache_utils.js +++ b/app/assets/javascripts/work_items/graphql/cache_utils.js @@ -239,6 +239,7 @@ export const setNewWorkItemCache = async ( descriptionHtml: '', lastEditedAt: null, lastEditedBy: null, + taskCompletionStatus: null, __typename: 'WorkItemWidgetDescription', }); diff --git a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql index d4be55debed8d9457e678b6fe0162079fdacc6bb..76ce3de770ee35fd4caafbfc72afffec252f288f 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql @@ -14,6 +14,10 @@ fragment WorkItemWidgets on WorkItemWidget { name webPath } + taskCompletionStatus { + completedCount + count + } } ... on WorkItemWidgetAssignees { allowsMultipleAssignees 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 27439051a75a4e3d263ca8bb763dd057947286c2..fdc9262b62031e1ef151cea22a3d35e37f4375f2 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 @@ -15,6 +15,10 @@ fragment WorkItemWidgets on WorkItemWidget { name webPath } + taskCompletionStatus { + completedCount + count + } } ... on WorkItemWidgetAssignees { allowsMultipleAssignees diff --git a/ee/spec/requests/api/graphql/group/work_items_spec.rb b/ee/spec/requests/api/graphql/group/work_items_spec.rb index eaed2eb17be09688813899cd73410e4b9d23774c..bba0c75e57839cfbb64f0b98e3af4db5a6374a92 100644 --- a/ee/spec/requests/api/graphql/group/work_items_spec.rb +++ b/ee/spec/requests/api/graphql/group/work_items_spec.rb @@ -117,6 +117,10 @@ webPath username } + taskCompletionStatus { + completedCount + count + } } ... on WorkItemWidgetAssignees { assignees { nodes { id } } diff --git a/spec/frontend/issues/show/components/edited_spec.js b/spec/frontend/issues/show/components/edited_spec.js index dc0c7f5be46689114f4bff4da10f6b724341dc58..4aac0b69212bcdae1f27de1c6c607f4d094efb76 100644 --- a/spec/frontend/issues/show/components/edited_spec.js +++ b/spec/frontend/issues/show/components/edited_spec.js @@ -18,16 +18,44 @@ describe('Edited component', () => { describe('task status section', () => { describe('task status text', () => { - it('renders when there is a task status', () => { - wrapper = mountComponent({ taskCompletionStatus: { completed_count: 1, count: 3 } }); + describe('when there is completion_count', () => { + it('renders when there is a task status', () => { + wrapper = mountComponent({ taskCompletionStatus: { completed_count: 1, count: 3 } }); - expect(wrapper.text()).toContain('1 of 3 checklist items completed'); + expect(wrapper.text()).toContain('1 of 3 checklist items completed'); + }); + + it('does not render when task count is 0', () => { + wrapper = mountComponent({ taskCompletionStatus: { completed_count: 0, count: 0 } }); + + expect(wrapper.text()).not.toContain('0 of 0 checklist items completed'); + }); + + it('renders "0 of x" when there is a task status and no items were checked yet', () => { + wrapper = mountComponent({ taskCompletionStatus: { completed_count: 0, count: 3 } }); + + expect(wrapper.text()).toContain('0 of 3 checklist items completed'); + }); }); - it('does not render when task count is 0', () => { - wrapper = mountComponent({ taskCompletionStatus: { completed_count: 0, count: 0 } }); + describe('when there is completionCount', () => { + it('renders when there is a task status', () => { + wrapper = mountComponent({ taskCompletionStatus: { completedCount: 1, count: 3 } }); + + expect(wrapper.text()).toContain('1 of 3 checklist items completed'); + }); + + it('does not render when task count is 0', () => { + wrapper = mountComponent({ taskCompletionStatus: { completedCount: 0, count: 0 } }); - expect(wrapper.text()).not.toContain('0 of 0 checklist items completed'); + expect(wrapper.text()).not.toContain('0 of 0 checklist items completed'); + }); + + it('renders "0 of x" when there is a task status and no items were checked yet', () => { + wrapper = mountComponent({ taskCompletionStatus: { completedCount: 0, count: 3 } }); + + expect(wrapper.text()).toContain('0 of 3 checklist items completed'); + }); }); }); diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js index 8b538a446389c79da24555cc0ca191aa532b7628..85f0661bdd39df70716e77a2b096e6a7bc813d33 100644 --- a/spec/frontend/work_items/components/work_item_description_spec.js +++ b/spec/frontend/work_items/components/work_item_description_spec.js @@ -239,4 +239,44 @@ describe('WorkItemDescription', () => { expect(wrapper.emitted('updateDraft')).toEqual([[updatedDesc]]); }); }); + + describe('checklist count visibility', () => { + const taskCompletionStatus = { + completedCount: 0, + count: 4, + }; + + describe('when checklist exists', () => { + it('when edit mode is active, checklist count is not visible', async () => { + await createComponent({ + editMode: true, + workItemResponse: workItemByIidResponseFactory({ taskCompletionStatus }), + }); + + expect(findEditedAt().exists()).toBe(false); + }); + + it('when edit mode is inactive, checklist count is visible', async () => { + await createComponent({ + editMode: false, + workItemResponse: workItemByIidResponseFactory({ taskCompletionStatus }), + }); + + expect(findEditedAt().exists()).toBe(true); + expect(findEditedAt().props()).toMatchObject({ + taskCompletionStatus, + }); + }); + }); + + describe('when checklist does not exist', () => { + it('checklist count is not visible', async () => { + await createComponent({ + workItemResponse: workItemByIidResponseFactory({ taskCompletionStatus: null }), + }); + + expect(findEditedAt().exists()).toBe(false); + }); + }); + }); }); diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index e561db31c61f823c2e58d4023a65da280150f24f..47f57bc318f0f962239447b330df4a2717dad571 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -169,6 +169,7 @@ export const workItemQueryResponse = { '<p data-sourcepos="1:1-1:19" dir="auto">some <strong>great</strong> text</p>', lastEditedAt: null, lastEditedBy: null, + taskCompletionStatus: null, }, { __typename: 'WorkItemWidgetAssignees', @@ -917,6 +918,7 @@ export const workItemResponseFactory = ({ allowsScopedLabels = false, lastEditedAt = null, lastEditedBy = null, + taskCompletionStatus = null, withCheckboxes = false, parent = mockParent.parent, workItemType = taskType, @@ -973,6 +975,7 @@ export const workItemResponseFactory = ({ : '<p data-sourcepos="1:1-1:19" dir="auto">some <strong>great</strong> text</p>', lastEditedAt, lastEditedBy, + taskCompletionStatus, }, assigneesWidgetPresent ? { @@ -4441,6 +4444,11 @@ export const createWorkItemQueryResponse = { webPath: '/root', __typename: 'UserCore', }, + taskCompletionStatus: { + completedCount: 0, + count: 4, + __typename: 'TaskCompletionStatus', + }, __typename: 'WorkItemWidgetDescription', }, {