Commit a97dbf68 authored by Kushal Pandya's avatar Kushal Pandya

Merge branch '334041-display-progress-of-epic-weight-on-epic-boards' into 'master'

Show progress of an epic on epic board cards

See merge request gitlab-org/gitlab!64622
parents adc62ff2 788e8143
<script> <script>
import { GlLabel, GlTooltip, GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import {
GlLabel,
GlTooltip,
GlTooltipDirective,
GlIcon,
GlLoadingIcon,
GlSprintf,
} from '@gitlab/ui';
import { sortBy } from 'lodash'; import { sortBy } from 'lodash';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner'; import boardCardInner from 'ee_else_ce/boards/mixins/board_card_inner';
...@@ -26,6 +33,7 @@ export default { ...@@ -26,6 +33,7 @@ export default {
IssueTimeEstimate, IssueTimeEstimate,
IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'), IssueCardWeight: () => import('ee_component/boards/components/issue_card_weight.vue'),
BoardBlockedIcon, BoardBlockedIcon,
GlSprintf,
}, },
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
...@@ -103,6 +111,9 @@ export default { ...@@ -103,6 +111,9 @@ export default {
shouldRenderEpicCountables() { shouldRenderEpicCountables() {
return this.isEpicBoard && this.item.hasIssues; return this.isEpicBoard && this.item.hasIssues;
}, },
shouldRenderEpicProgress() {
return this.totalWeight > 0;
},
showLabelFooter() { showLabelFooter() {
return this.isShowingLabels && this.item.labels.find(this.showLabel); return this.isShowingLabels && this.item.labels.find(this.showLabel);
}, },
...@@ -130,6 +141,9 @@ export default { ...@@ -130,6 +141,9 @@ export default {
this.item.descendantWeightSum.openedIssues + this.item.descendantWeightSum.closedIssues this.item.descendantWeightSum.openedIssues + this.item.descendantWeightSum.closedIssues
); );
}, },
totalProgress() {
return Math.round((this.item.descendantWeightSum.closedIssues / this.totalWeight) * 100);
},
}, },
methods: { methods: {
...mapActions(['performSearch', 'setError']), ...mapActions(['performSearch', 'setError']),
...@@ -246,40 +260,51 @@ export default { ...@@ -246,40 +260,51 @@ export default {
<gl-tooltip :target="() => $refs.countBadge" data-testid="epic-countables-tooltip"> <gl-tooltip :target="() => $refs.countBadge" data-testid="epic-countables-tooltip">
<p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0"> <p v-if="allowSubEpics" class="gl-font-weight-bold gl-m-0">
{{ __('Epics') }} &#8226; {{ __('Epics') }} &#8226;
<span class="gl-font-weight-normal" <span class="gl-font-weight-normal">
>{{ <gl-sprintf :message="__('%{openedEpics} open, %{closedEpics} closed')">
sprintf(__('%{openedEpics} open, %{closedEpics} closed'), { <template #openedEpics>{{ item.descendantCounts.openedEpics }}</template>
openedEpics: item.descendantCounts.openedEpics, <template #closedEpics>{{ item.descendantCounts.closedEpics }}</template>
closedEpics: item.descendantCounts.closedEpics, </gl-sprintf>
})
}}
</span> </span>
</p> </p>
<p class="gl-font-weight-bold gl-m-0"> <p class="gl-font-weight-bold gl-m-0">
{{ __('Issues') }} &#8226; {{ __('Issues') }} &#8226;
<span class="gl-font-weight-normal" <span class="gl-font-weight-normal">
>{{ <gl-sprintf :message="__('%{openedIssues} open, %{closedIssues} closed')">
sprintf(__('%{openedIssues} open, %{closedIssues} closed'), { <template #openedIssues>{{ item.descendantCounts.openedIssues }}</template>
openedIssues: item.descendantCounts.openedIssues, <template #closedIssues>{{ item.descendantCounts.closedIssues }}</template>
closedIssues: item.descendantCounts.closedIssues, </gl-sprintf>
})
}}
</span> </span>
</p> </p>
<p class="gl-font-weight-bold gl-m-0"> <p class="gl-font-weight-bold gl-m-0">
{{ __('Weight') }} &#8226; {{ __('Total weight') }} &#8226;
<span class="gl-font-weight-normal" data-testid="epic-countables-total-weight" <span class="gl-font-weight-normal" data-testid="epic-countables-total-weight">
>{{ {{ totalWeight }}
sprintf(__('%{closedWeight} complete, %{openWeight} incomplete'), {
openWeight: item.descendantWeightSum.openedIssues,
closedWeight: item.descendantWeightSum.closedIssues,
})
}}
</span> </span>
</p> </p>
</gl-tooltip> </gl-tooltip>
<span ref="countBadge" class="issue-count-badge board-card-info"> <gl-tooltip
v-if="shouldRenderEpicProgress"
:target="() => $refs.progressBadge"
data-testid="epic-progress-tooltip"
>
<p class="gl-font-weight-bold gl-m-0">
{{ __('Progress') }} &#8226;
<span class="gl-font-weight-normal" data-testid="epic-progress-tooltip-content">
<gl-sprintf
:message="__('%{completedWeight} of %{totalWeight} weight completed')"
>
<template #completedWeight>{{
item.descendantWeightSum.closedIssues
}}</template>
<template #totalWeight>{{ totalWeight }}</template>
</gl-sprintf>
</span>
</p>
</gl-tooltip>
<span ref="countBadge" class="issue-count-badge board-card-info gl-mr-0 gl-pr-0">
<span v-if="allowSubEpics" class="gl-mr-3"> <span v-if="allowSubEpics" class="gl-mr-3">
<gl-icon name="epic" /> <gl-icon name="epic" />
{{ totalEpicsCount }} {{ totalEpicsCount }}
...@@ -293,6 +318,17 @@ export default { ...@@ -293,6 +318,17 @@ export default {
{{ totalWeight }} {{ totalWeight }}
</span> </span>
</span> </span>
<span
v-if="shouldRenderEpicProgress"
ref="progressBadge"
class="issue-count-badge board-card-info gl-pl-0"
>
<span class="gl-mr-3" data-testid="epic-progress">
<gl-icon name="progress" />
{{ totalProgress }}%
</span>
</span>
</span> </span>
<span v-if="!isEpicBoard"> <span v-if="!isEpicBoard">
<issue-due-date <issue-due-date
......
...@@ -130,13 +130,14 @@ You can filter by the following: ...@@ -130,13 +130,14 @@ You can filter by the following:
- Author - Author
- Label - Label
### View count of issues and weight in an epic ### View count of issues, weight, and progress of an epic
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331330) in GitLab 14.1. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331330) in GitLab 14.1.
Epics on an epic board show a summary of their issues and weight. Epics on an epic board show a summary of their issues, weight, and progress.
To see the number of open and closed issues and the completed and incomplete weight, To see the number of open and closed issues and the completed and incomplete
hover over the issues icon **{issues}** or weight icon **{weight}**. weight, hover over the issues icon **{issues}**, weight icon **{weight}**, or
progress icon **{progress}**.
### Move epics and lists ### Move epics and lists
......
...@@ -444,9 +444,6 @@ msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests" ...@@ -444,9 +444,6 @@ msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "%{closedWeight} complete, %{openWeight} incomplete"
msgstr ""
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements." msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
msgstr "" msgstr ""
......
...@@ -40,7 +40,9 @@ describe('Board card component', () => { ...@@ -40,7 +40,9 @@ describe('Board card component', () => {
const findEpicCountables = () => wrapper.findByTestId('epic-countables'); const findEpicCountables = () => wrapper.findByTestId('epic-countables');
const findEpicCountablesBadgeIssues = () => wrapper.findByTestId('epic-countables-counts-issues'); const findEpicCountablesBadgeIssues = () => wrapper.findByTestId('epic-countables-counts-issues');
const findEpicCountablesBadgeWeight = () => wrapper.findByTestId('epic-countables-weight-issues'); const findEpicCountablesBadgeWeight = () => wrapper.findByTestId('epic-countables-weight-issues');
const findEpicBadgeProgress = () => wrapper.findByTestId('epic-progress');
const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight'); const findEpicCountablesTotalWeight = () => wrapper.findByTestId('epic-countables-total-weight');
const findEpicProgressTooltip = () => wrapper.findByTestId('epic-progress-tooltip-content');
const createStore = ({ isEpicBoard = false } = {}) => { const createStore = ({ isEpicBoard = false } = {}) => {
store = new Vuex.Store({ store = new Vuex.Store({
...@@ -466,7 +468,7 @@ describe('Board card component', () => { ...@@ -466,7 +468,7 @@ describe('Board card component', () => {
expect(findEpicCountablesBadgeIssues().exists()).toBe(false); expect(findEpicCountablesBadgeIssues().exists()).toBe(false);
}); });
it('shows render item countBadge and weights correctly', () => { it('shows render item countBadge, weights, and progress correctly', () => {
createWrapper({ createWrapper({
item: { item: {
...issue, ...issue,
...@@ -475,15 +477,32 @@ describe('Board card component', () => { ...@@ -475,15 +477,32 @@ describe('Board card component', () => {
openedIssues: 1, openedIssues: 1,
}, },
descendantWeightSum: { descendantWeightSum: {
...descendantWeightSum, closedIssues: 10,
openedIssues: 2, openedIssues: 5,
}, },
hasIssues: true, hasIssues: true,
}, },
}); });
expect(findEpicCountablesBadgeIssues().text()).toBe('1'); expect(findEpicCountablesBadgeIssues().text()).toBe('1');
expect(findEpicCountablesBadgeWeight().text()).toBe('2'); expect(findEpicCountablesBadgeWeight().text()).toBe('15');
expect(findEpicBadgeProgress().text()).toBe('67%');
});
it('does not render progress when weight is zero', () => {
createWrapper({
item: {
...issue,
descendantCounts: {
...descendantCounts,
openedIssues: 1,
},
descendantWeightSum,
hasIssues: true,
},
});
expect(findEpicBadgeProgress().exists()).toBe(false);
}); });
it('renders the tooltip with the correct data', () => { it('renders the tooltip with the correct data', () => {
...@@ -502,7 +521,8 @@ describe('Board card component', () => { ...@@ -502,7 +521,8 @@ describe('Board card component', () => {
const tooltip = findEpicCountablesTotalTooltip(); const tooltip = findEpicCountablesTotalTooltip();
expect(tooltip).toBeDefined(); expect(tooltip).toBeDefined();
expect(findEpicCountablesTotalWeight().text()).toBe('10 complete, 5 incomplete'); expect(findEpicCountablesTotalWeight().text()).toBe('15');
expect(findEpicProgressTooltip().text()).toBe('10 of 15 weight completed');
}); });
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment