Commit 86aac4ae authored by Andrew Smith's avatar Andrew Smith

feat: Display epic and issue labels when viewing an epic

Fixes https://gitlab.com/gitlab-org/gitlab/-/issues/345205
Changelog: added
EE: true
parent 30cfc3c3
...@@ -45,6 +45,16 @@ fragment EpicNode on Epic { ...@@ -45,6 +45,16 @@ fragment EpicNode on Epic {
confidential confidential
hasChildren hasChildren
hasIssues hasIssues
labels {
__typename
nodes {
__typename
color
description
textColor
title
}
}
group { group {
__typename __typename
id id
...@@ -127,6 +137,16 @@ query childItems( ...@@ -127,6 +137,16 @@ query childItems(
dueDate dueDate
} }
healthStatus healthStatus
labels {
__typename
nodes {
__typename
color
description
textColor
title
}
}
} }
} }
pageInfo { pageInfo {
......
...@@ -3,6 +3,7 @@ import { GlTooltip, GlIcon } from '@gitlab/ui'; ...@@ -3,6 +3,7 @@ import { GlTooltip, GlIcon } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import { issuableTypesMap } from '~/related_issues/constants'; import { issuableTypesMap } from '~/related_issues/constants';
import ToggleLabels from '../../boards/components/toggle_labels.vue';
import EpicHealthStatus from './epic_health_status.vue'; import EpicHealthStatus from './epic_health_status.vue';
import EpicActionsSplitButton from './epic_issue_actions_split_button.vue'; import EpicActionsSplitButton from './epic_issue_actions_split_button.vue';
...@@ -13,6 +14,7 @@ export default { ...@@ -13,6 +14,7 @@ export default {
GlIcon, GlIcon,
EpicHealthStatus, EpicHealthStatus,
EpicActionsSplitButton, EpicActionsSplitButton,
ToggleLabels,
}, },
computed: { computed: {
...mapState([ ...mapState([
...@@ -120,6 +122,11 @@ export default { ...@@ -120,6 +122,11 @@ export default {
</div> </div>
<epic-health-status v-if="showHealthStatus" :health-status="healthStatus" /> <epic-health-status v-if="showHealthStatus" :health-status="healthStatus" />
</div> </div>
<div class="gl-display-inline-flex gl-mr-3">
<toggle-labels />
</div>
<div <div
v-if="parentItem.userPermissions.adminEpic" v-if="parentItem.userPermissions.adminEpic"
class="d-inline-flex flex-column flex-sm-row js-button-container" class="d-inline-flex flex-column flex-sm-row js-button-container"
......
<script> <script>
import { import {
GlTooltipDirective,
GlModalDirective,
GlLink,
GlIcon,
GlButton, GlButton,
GlIcon,
GlLabel,
GlLink,
GlModalDirective,
GlTooltip, GlTooltip,
GlTooltipDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { isEmpty, isNumber } from 'lodash'; import { isEmpty, isNumber } from 'lodash';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
...@@ -13,6 +14,7 @@ import { mapState, mapActions } from 'vuex'; ...@@ -13,6 +14,7 @@ import { mapState, mapActions } from 'vuex';
import ItemWeight from 'ee/boards/components/issue_card_weight.vue'; import ItemWeight from 'ee/boards/components/issue_card_weight.vue';
import ItemDueDate from '~/boards/components/issue_due_date.vue'; import ItemDueDate from '~/boards/components/issue_due_date.vue';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { isScopedLabel } from '~/lib/utils/common_utils';
import ItemAssignees from '~/vue_shared/components/issue/issue_assignees.vue'; import ItemAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
import ItemMilestone from '~/vue_shared/components/issue/issue_milestone.vue'; import ItemMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
...@@ -27,6 +29,7 @@ export default { ...@@ -27,6 +29,7 @@ export default {
itemRemoveModalId, itemRemoveModalId,
components: { components: {
GlIcon, GlIcon,
GlLabel,
GlLink, GlLink,
GlTooltip, GlTooltip,
GlButton, GlButton,
...@@ -47,13 +50,27 @@ export default { ...@@ -47,13 +50,27 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
labelsFilterParam: {
type: String,
required: false,
default: 'label_name',
},
item: { item: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
computed: { computed: {
...mapState(['childrenFlags', 'userSignedIn', 'allowSubEpics', 'allowIssuableHealthStatus']), ...mapState([
'allowIssuableHealthStatus',
'allowScopedLabels',
'allowSubEpics',
'childrenFlags',
'epicsWebUrl',
'isShowingLabels',
'issuesWebUrl',
'userSignedIn',
]),
itemReference() { itemReference() {
return this.item.reference; return this.item.reference;
}, },
...@@ -81,6 +98,9 @@ export default { ...@@ -81,6 +98,9 @@ export default {
hasWeight() { hasWeight() {
return isNumber(this.item.weight); return isNumber(this.item.weight);
}, },
showLabels() {
return this.isShowingLabels && this.item.labels?.length > 0;
},
stateText() { stateText() {
return this.isOpen ? __('Opened') : __('Closed'); return this.isOpen ? __('Opened') : __('Closed');
}, },
...@@ -159,6 +179,18 @@ export default { ...@@ -159,6 +179,18 @@ export default {
item, item,
}); });
}, },
showScopedLabel(label) {
return isScopedLabel(label) && this.allowScopedLabels;
},
labelFilterUrl(label) {
let basePath = this.issuesWebUrl;
if (this.isEpic) {
basePath = this.epicsWebUrl;
}
return `${basePath}?${this.labelsFilterParam}[]=${encodeURIComponent(label.title)}`;
},
}, },
}; };
</script> </script>
...@@ -211,7 +243,7 @@ export default { ...@@ -211,7 +243,7 @@ export default {
</div> </div>
<div <div
class="item-meta gl-display-flex gl-flex-wrap mt-xl-0 flex-xl-nowrap gl-align-items-center gl-py-2 gl-ml-6" class="item-meta gl-display-flex gl-flex-wrap mt-xl-0 gl-align-items-center gl-py-2 gl-ml-6"
> >
<span class="gl-mr-5">{{ itemHierarchy }}</span> <span class="gl-mr-5">{{ itemHierarchy }}</span>
<gl-tooltip v-if="isEpic" :target="() => $refs.countBadge"> <gl-tooltip v-if="isEpic" :target="() => $refs.countBadge">
...@@ -288,14 +320,28 @@ export default { ...@@ -288,14 +320,28 @@ export default {
v-if="showEpicHealthStatus" v-if="showEpicHealthStatus"
:health-status="item.healthStatus" :health-status="item.healthStatus"
data-testid="epic-health-status" data-testid="epic-health-status"
class="issuable-tag-valign" class="issuable-tag-valign gl-mr-5"
/> />
<issue-health-status <issue-health-status
v-if="showIssueHealthStatus" v-if="showIssueHealthStatus"
:health-status="item.healthStatus" :health-status="item.healthStatus"
data-testid="issue-health-status" data-testid="issue-health-status"
class="issuable-tag-valign" class="issuable-tag-valign gl-mr-5"
/>
<template v-if="showLabels">
<gl-label
v-for="label in item.labels"
:key="label.id"
:background-color="label.color"
:description="label.description"
:scoped="showScopedLabel(label)"
:target="labelFilterUrl(label)"
:title="label.title"
class="gl-mr-5 gl-mt-1 gl-mb-1 gl-label-sm"
tooltip-placement="top"
/> />
</template>
</div> </div>
</div> </div>
......
...@@ -27,8 +27,9 @@ export default () => { ...@@ -27,8 +27,9 @@ export default () => {
autoCompleteEpics, autoCompleteEpics,
autoCompleteIssues, autoCompleteIssues,
userSignedIn, userSignedIn,
allowSubEpics,
allowIssuableHealthStatus, allowIssuableHealthStatus,
allowScopedLabels,
allowSubEpics,
} = el.dataset; } = el.dataset;
const initialData = JSON.parse(el.dataset.initial); const initialData = JSON.parse(el.dataset.initial);
...@@ -63,8 +64,11 @@ export default () => { ...@@ -63,8 +64,11 @@ export default () => {
autoCompleteEpics: parseBoolean(autoCompleteEpics), autoCompleteEpics: parseBoolean(autoCompleteEpics),
autoCompleteIssues: parseBoolean(autoCompleteIssues), autoCompleteIssues: parseBoolean(autoCompleteIssues),
userSignedIn: parseBoolean(userSignedIn), userSignedIn: parseBoolean(userSignedIn),
allowSubEpics: parseBoolean(allowSubEpics),
allowIssuableHealthStatus: parseBoolean(allowIssuableHealthStatus), allowIssuableHealthStatus: parseBoolean(allowIssuableHealthStatus),
allowScopedLabels: parseBoolean(allowScopedLabels),
allowSubEpics: parseBoolean(allowSubEpics),
epicsWebUrl: initialData.epicsWebUrl,
issuesWebUrl: initialData.issuesWebUrl,
}); });
}, },
methods: { methods: {
......
...@@ -624,3 +624,7 @@ export const fetchDescendantGroups = ({ commit }, { groupId, search = '' }) => { ...@@ -624,3 +624,7 @@ export const fetchDescendantGroups = ({ commit }, { groupId, search = '' }) => {
commit(types.RECEIVE_DESCENDANT_GROUPS_FAILURE); commit(types.RECEIVE_DESCENDANT_GROUPS_FAILURE);
}); });
}; };
export const setShowLabels = ({ commit }, val) => {
commit(types.SET_SHOW_LABELS, val);
};
...@@ -53,3 +53,5 @@ export const RECIEVE_PROJECTS_FAILURE = 'RECIEVE_PROJECTS_FAILURE'; ...@@ -53,3 +53,5 @@ export const RECIEVE_PROJECTS_FAILURE = 'RECIEVE_PROJECTS_FAILURE';
export const REQUEST_DESCENDANT_GROUPS = 'REQUEST_DESCENDANT_GROUPS'; export const REQUEST_DESCENDANT_GROUPS = 'REQUEST_DESCENDANT_GROUPS';
export const RECEIVE_DESCENDANT_GROUPS_SUCCESS = 'RECEIVE_DESCENDANT_GROUPS_SUCCESS'; export const RECEIVE_DESCENDANT_GROUPS_SUCCESS = 'RECEIVE_DESCENDANT_GROUPS_SUCCESS';
export const RECEIVE_DESCENDANT_GROUPS_FAILURE = 'RECEIVE_DESCENDANT_GROUPS_FAILURE'; export const RECEIVE_DESCENDANT_GROUPS_FAILURE = 'RECEIVE_DESCENDANT_GROUPS_FAILURE';
export const SET_SHOW_LABELS = 'SET_SHOW_LABELS';
...@@ -13,8 +13,12 @@ export default { ...@@ -13,8 +13,12 @@ export default {
autoCompleteIssues, autoCompleteIssues,
projectsEndpoint, projectsEndpoint,
userSignedIn, userSignedIn,
allowSubEpics,
allowIssuableHealthStatus, allowIssuableHealthStatus,
allowScopedLabels,
allowSubEpics,
epicsWebUrl,
isShowingLabels,
issuesWebUrl,
}, },
) { ) {
state.epicsEndpoint = epicsEndpoint; state.epicsEndpoint = epicsEndpoint;
...@@ -23,8 +27,12 @@ export default { ...@@ -23,8 +27,12 @@ export default {
state.autoCompleteIssues = autoCompleteIssues; state.autoCompleteIssues = autoCompleteIssues;
state.projectsEndpoint = projectsEndpoint; state.projectsEndpoint = projectsEndpoint;
state.userSignedIn = userSignedIn; state.userSignedIn = userSignedIn;
state.allowSubEpics = allowSubEpics;
state.allowIssuableHealthStatus = allowIssuableHealthStatus; state.allowIssuableHealthStatus = allowIssuableHealthStatus;
state.allowScopedLabels = allowScopedLabels;
state.allowSubEpics = allowSubEpics;
state.epicsWebUrl = epicsWebUrl;
state.isShowingLabels = isShowingLabels;
state.issuesWebUrl = issuesWebUrl;
}, },
[types.SET_INITIAL_PARENT_ITEM](state, data) { [types.SET_INITIAL_PARENT_ITEM](state, data) {
...@@ -279,4 +287,7 @@ export default { ...@@ -279,4 +287,7 @@ export default {
[types.RECEIVE_DESCENDANT_GROUPS_FAILURE](state) { [types.RECEIVE_DESCENDANT_GROUPS_FAILURE](state) {
state.descendantGroupsFetchInProgress = false; state.descendantGroupsFetchInProgress = false;
}, },
[types.SET_SHOW_LABELS](state, val) {
state.isShowingLabels = val;
},
}; };
...@@ -47,8 +47,12 @@ export default () => ({ ...@@ -47,8 +47,12 @@ export default () => ({
showCreateIssueForm: false, showCreateIssueForm: false,
autoCompleteEpics: false, autoCompleteEpics: false,
autoCompleteIssues: false, autoCompleteIssues: false,
allowSubEpics: false,
allowIssuableHealthStatus: false, allowIssuableHealthStatus: false,
allowScopedLabels: false,
allowSubEpics: false,
epicsWebUrl: '',
issuesWebUrl: '',
isShowingLabels: false,
removeItemModalProps: { removeItemModalProps: {
parentItem: {}, parentItem: {},
......
...@@ -51,6 +51,11 @@ export const applySorts = (array) => array.sort(sortChildren).sort(sortByState); ...@@ -51,6 +51,11 @@ export const applySorts = (array) => array.sort(sortChildren).sort(sortByState);
*/ */
export const formatChildItem = (item) => ({ ...item, pathIdSeparator: PathIdSeparator[item.type] }); export const formatChildItem = (item) => ({ ...item, pathIdSeparator: PathIdSeparator[item.type] });
export const extractLabels = (labels) =>
labels.nodes.map((labelNode) => ({
...labelNode,
}));
/** /**
* Returns formatted array of Epics that doesn't contain * Returns formatted array of Epics that doesn't contain
* `edges`->`node` nesting * `edges`->`node` nesting
...@@ -63,6 +68,7 @@ export const extractChildEpics = (children) => ...@@ -63,6 +68,7 @@ export const extractChildEpics = (children) =>
...epicNode, ...epicNode,
fullPath: epicNode.group.fullPath, fullPath: epicNode.group.fullPath,
type: ChildType.Epic, type: ChildType.Epic,
labels: extractLabels(epicNode.labels),
}), }),
); );
...@@ -89,6 +95,7 @@ export const extractChildIssues = (issues) => ...@@ -89,6 +95,7 @@ export const extractChildIssues = (issues) =>
...issueNode, ...issueNode,
type: ChildType.Issue, type: ChildType.Issue,
assignees: extractIssueAssignees(issueNode.assignees), assignees: extractIssueAssignees(issueNode.assignees),
labels: extractLabels(issueNode.labels),
}), }),
); );
......
...@@ -19,11 +19,13 @@ module EE ...@@ -19,11 +19,13 @@ module EE
) )
if parent.is_a?(Group) if parent.is_a?(Group)
data[:issueLinksEndpoint] = group_epic_issues_path(parent, issuable) data[:confidential] = issuable.confidential
data[:epicLinksEndpoint] = group_epic_links_path(parent, issuable) data[:epicLinksEndpoint] = group_epic_links_path(parent, issuable)
data[:epicsWebUrl] = group_epics_path(parent)
data[:fullPath] = parent.full_path data[:fullPath] = parent.full_path
data[:issueLinksEndpoint] = group_epic_issues_path(parent, issuable)
data[:issuesWebUrl] = issues_group_path(parent)
data[:projectsEndpoint] = expose_path(api_v4_groups_projects_path(id: parent.id)) data[:projectsEndpoint] = expose_path(api_v4_groups_projects_path(id: parent.id))
data[:confidential] = issuable.confidential
end end
data data
......
...@@ -5,9 +5,11 @@ ...@@ -5,9 +5,11 @@
- epic_reference = @epic.to_reference - epic_reference = @epic.to_reference
- sub_epics_feature_available = @group.licensed_feature_available?(:subepics) - sub_epics_feature_available = @group.licensed_feature_available?(:subepics)
- issuable_health_status_feature_available = @group.licensed_feature_available?(:issuable_health_status) - issuable_health_status_feature_available = @group.licensed_feature_available?(:issuable_health_status)
- scoped_labels_feature_available = @group.licensed_feature_available?(:scoped_labels)
- allow_sub_epics = sub_epics_feature_available ? 'true' : 'false' - allow_sub_epics = sub_epics_feature_available ? 'true' : 'false'
- allow_issuable_health_status = issuable_health_status_feature_available ? 'true' : 'false' - allow_issuable_health_status = issuable_health_status_feature_available ? 'true' : 'false'
- allow_scoped_labels = scoped_labels_feature_available ? 'true' : 'false'
- add_to_breadcrumbs _("Epics"), group_epics_path(@group) - add_to_breadcrumbs _("Epics"), group_epics_path(@group)
- breadcrumb_title epic_reference - breadcrumb_title epic_reference
...@@ -54,8 +56,9 @@ ...@@ -54,8 +56,9 @@
auto_complete_epics: allow_sub_epics, auto_complete_epics: allow_sub_epics,
auto_complete_issues: 'true', auto_complete_issues: 'true',
user_signed_in: current_user.present? ? 'true' : 'false', user_signed_in: current_user.present? ? 'true' : 'false',
allow_sub_epics: allow_sub_epics,
allow_issuable_health_status: allow_issuable_health_status, allow_issuable_health_status: allow_issuable_health_status,
allow_scoped_labels: allow_scoped_labels,
allow_sub_epics: allow_sub_epics,
initial: issuable_initial_data(@epic).to_json } } initial: issuable_initial_data(@epic).to_json } }
- if sub_epics_feature_available - if sub_epics_feature_available
#roadmap.tab-pane.gl-display-none #roadmap.tab-pane.gl-display-none
......
...@@ -5,6 +5,7 @@ import Vuex from 'vuex'; ...@@ -5,6 +5,7 @@ import Vuex from 'vuex';
import EpicHealthStatus from 'ee/related_items_tree/components/epic_health_status.vue'; import EpicHealthStatus from 'ee/related_items_tree/components/epic_health_status.vue';
import EpicActionsSplitButton from 'ee/related_items_tree/components/epic_issue_actions_split_button.vue'; import EpicActionsSplitButton from 'ee/related_items_tree/components/epic_issue_actions_split_button.vue';
import RelatedItemsTreeHeader from 'ee/related_items_tree/components/related_items_tree_header.vue'; import RelatedItemsTreeHeader from 'ee/related_items_tree/components/related_items_tree_header.vue';
import ToggleLabels from 'ee/boards/components/toggle_labels.vue';
import createDefaultStore from 'ee/related_items_tree/store'; import createDefaultStore from 'ee/related_items_tree/store';
import * as epicUtils from 'ee/related_items_tree/utils/epic_utils'; import * as epicUtils from 'ee/related_items_tree/utils/epic_utils';
...@@ -78,6 +79,16 @@ describe('RelatedItemsTree', () => { ...@@ -78,6 +79,16 @@ describe('RelatedItemsTree', () => {
}); });
}); });
describe('toggleLabels', () => {
beforeEach(() => {
wrapper = createComponent();
});
it('toggle labels component is visible', () => {
expect(wrapper.find(ToggleLabels).isVisible()).toBe(true);
});
});
describe('epic issue actions split button', () => { describe('epic issue actions split button', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent(); wrapper = createComponent();
......
import { GlButton, GlLink, GlIcon } from '@gitlab/ui'; import { GlButton, GlLabel, GlLink, GlIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import ItemWeight from 'ee/boards/components/issue_card_weight.vue'; import ItemWeight from 'ee/boards/components/issue_card_weight.vue';
...@@ -39,6 +39,7 @@ const createIssueItem = (mockIssue = mockIssue1) => { ...@@ -39,6 +39,7 @@ const createIssueItem = (mockIssue = mockIssue1) => {
type: ChildType.Issue, type: ChildType.Issue,
pathIdSeparator: PathIdSeparator.Issue, pathIdSeparator: PathIdSeparator.Issue,
assignees: epicUtils.extractIssueAssignees(mockIssue.assignees), assignees: epicUtils.extractIssueAssignees(mockIssue.assignees),
labels: epicUtils.extractLabels(mockIssue.labels),
}; };
}; };
...@@ -48,6 +49,7 @@ const createEpicItem = (mockEpic = mockOpenEpic, mockEpicMeta = mockEpicMeta1) = ...@@ -48,6 +49,7 @@ const createEpicItem = (mockEpic = mockOpenEpic, mockEpicMeta = mockEpicMeta1) =
type: ChildType.Epic, type: ChildType.Epic,
pathIdSeparator: PathIdSeparator.Epic, pathIdSeparator: PathIdSeparator.Epic,
...mockEpicMeta, ...mockEpicMeta,
labels: epicUtils.extractLabels(mockEpic.labels),
}; };
}; };
...@@ -80,9 +82,10 @@ describe('RelatedItemsTree', () => { ...@@ -80,9 +82,10 @@ describe('RelatedItemsTree', () => {
describe('TreeItemBody', () => { describe('TreeItemBody', () => {
let wrapper; let wrapper;
const findChildLabels = () => wrapper.findAll(GlLabel);
const findCountBadge = () => wrapper.find({ ref: 'countBadge' }); const findCountBadge = () => wrapper.find({ ref: 'countBadge' });
const findIssueHealthStatus = () => wrapper.find('[data-testid="issue-health-status"]');
const findEpicHealthStatus = () => wrapper.find('[data-testid="epic-health-status"]'); const findEpicHealthStatus = () => wrapper.find('[data-testid="epic-health-status"]');
const findIssueHealthStatus = () => wrapper.find('[data-testid="issue-health-status"]');
const findIssueIcon = () => wrapper.find({ ref: 'stateIconMd' }); const findIssueIcon = () => wrapper.find({ ref: 'stateIconMd' });
const findLink = () => wrapper.findComponent(GlLink); const findLink = () => wrapper.findComponent(GlLink);
const enableHealthStatus = () => { const enableHealthStatus = () => {
...@@ -91,6 +94,11 @@ describe('RelatedItemsTree', () => { ...@@ -91,6 +94,11 @@ describe('RelatedItemsTree', () => {
allowIssuableHealthStatus: true, allowIssuableHealthStatus: true,
}); });
}; };
const setShowLabels = (isShowingLabels) => {
wrapper.vm.$store.dispatch('setShowLabels', isShowingLabels);
return nextTick();
};
beforeEach(() => { beforeEach(() => {
mockItem = createIssueItem(); mockItem = createIssueItem();
...@@ -180,6 +188,36 @@ describe('RelatedItemsTree', () => { ...@@ -180,6 +188,36 @@ describe('RelatedItemsTree', () => {
}); });
}); });
describe('when toggling labels on', () => {
it('returns true when `item.labels` is defined and has values', async () => {
expect(findChildLabels().length).toBe(0);
await setShowLabels(true);
const labels = findChildLabels();
expect(labels.length).toBe(1);
const firstLabel = labels.at(0);
expect(firstLabel.props('backgroundColor')).toBe(mockIssue1.labels.nodes[0].color);
expect(firstLabel.props('description')).toBe(mockIssue1.labels.nodes[0].description);
expect(firstLabel.props('title')).toBe(mockIssue1.labels.nodes[0].title);
});
});
describe('when toggling labels off', () => {
it('returns true when `item.labels` is defined and has values', async () => {
await setShowLabels(true);
expect(findChildLabels().length).toBe(1);
await setShowLabels(false);
expect(findChildLabels().length).toBe(0);
});
});
describe('stateText', () => { describe('stateText', () => {
it('returns string `Opened` when `item.state` value is `opened`', () => { it('returns string `Opened` when `item.state` value is `opened`', () => {
wrapper.setProps({ wrapper.setProps({
...@@ -316,6 +354,21 @@ describe('RelatedItemsTree', () => { ...@@ -316,6 +354,21 @@ describe('RelatedItemsTree', () => {
}); });
}); });
}); });
describe.each`
createItem | expectedFilterUrl | itemType
${createEpicItem} | ${`${mockInitialConfig.epicsWebUrl}?label_name[]=Label`} | ${'epic'}
${createIssueItem} | ${`${mockInitialConfig.issuesWebUrl}?label_name[]=Label`} | ${'issue'}
`('labelFilterUrl', ({ createItem, expectedFilterUrl, itemType }) => {
beforeEach(() => {
mockItem = createItem();
wrapper = createComponent();
});
it(`filterURL for ${itemType} should be ${expectedFilterUrl}`, () => {
expect(wrapper.vm.labelFilterUrl(mockItem.labels[0])).toBe(expectedFilterUrl);
});
});
}); });
describe('template', () => { describe('template', () => {
......
...@@ -9,6 +9,9 @@ export const mockInitialConfig = { ...@@ -9,6 +9,9 @@ export const mockInitialConfig = {
autoCompleteIssues: false, autoCompleteIssues: false,
userSignedIn: true, userSignedIn: true,
allowSubEpics: true, allowSubEpics: true,
isShowingLabels: false,
epicsWebUrl: `${TEST_HOST}/groups/gitlab-org/-/epics`,
issuesWebUrl: `${TEST_HOST}/groups/gitlab-org/-/issues`,
}; };
export const mockParentItem = { export const mockParentItem = {
...@@ -36,6 +39,16 @@ export const mockParentItem = { ...@@ -36,6 +39,16 @@ export const mockParentItem = {
issuesAtRisk: 0, issuesAtRisk: 0,
issuesNeedingAttention: 1, issuesNeedingAttention: 1,
}, },
labels: {
nodes: [
{
color: '#ff0000',
description: 'Mock Label',
textColor: '#ffffff',
title: 'Label',
},
],
},
}; };
export const mockParentItem2 = { export const mockParentItem2 = {
...@@ -60,6 +73,16 @@ export const mockParentItem2 = { ...@@ -60,6 +73,16 @@ export const mockParentItem2 = {
issuesAtRisk: 0, issuesAtRisk: 0,
issuesNeedingAttention: 1, issuesNeedingAttention: 1,
}, },
labels: {
nodes: [
{
color: '#ff0000',
description: 'Mock Label',
textColor: '#ffffff',
title: 'Label',
},
],
},
}; };
export const mockEpic1 = { export const mockEpic1 = {
...@@ -86,6 +109,16 @@ export const mockEpic1 = { ...@@ -86,6 +109,16 @@ export const mockEpic1 = {
issuesNeedingAttention: 0, issuesNeedingAttention: 0,
issuesOnTrack: 0, issuesOnTrack: 0,
}, },
labels: {
nodes: [
{
color: '#ff0000',
description: 'Mock Label',
textColor: '#ffffff',
title: 'Label',
},
],
},
}; };
export const mockEpic2 = { export const mockEpic2 = {
...@@ -112,6 +145,16 @@ export const mockEpic2 = { ...@@ -112,6 +145,16 @@ export const mockEpic2 = {
issuesNeedingAttention: 0, issuesNeedingAttention: 0,
issuesOnTrack: 0, issuesOnTrack: 0,
}, },
labels: {
nodes: [
{
color: '#ff0000',
description: 'Mock Label',
textColor: '#ffffff',
title: 'Label',
},
],
},
}; };
// Epic meta data for having some open issues // Epic meta data for having some open issues
...@@ -191,6 +234,16 @@ export const mockIssue1 = { ...@@ -191,6 +234,16 @@ export const mockIssue1 = {
dueDate: '2019-06-30', dueDate: '2019-06-30',
}, },
healthStatus: 'onTrack', healthStatus: 'onTrack',
labels: {
nodes: [
{
color: '#ff0000',
description: 'Mock Label',
textColor: '#ffffff',
title: 'Label',
},
],
},
}; };
export const mockIssue2 = { export const mockIssue2 = {
...@@ -211,6 +264,9 @@ export const mockIssue2 = { ...@@ -211,6 +264,9 @@ export const mockIssue2 = {
}, },
milestone: null, milestone: null,
healthStatus: 'needsAttention', healthStatus: 'needsAttention',
labels: {
nodes: [],
},
}; };
export const mockClosedIssue = { export const mockClosedIssue = {
...@@ -231,6 +287,9 @@ export const mockClosedIssue = { ...@@ -231,6 +287,9 @@ export const mockClosedIssue = {
}, },
milestone: null, milestone: null,
healthStatus: 'atRisk', healthStatus: 'atRisk',
labels: {
nodes: [],
},
}; };
export const mockEpics = [mockEpic1, mockEpic2]; export const mockEpics = [mockEpic1, mockEpic2];
......
...@@ -19,27 +19,29 @@ RSpec.describe IssuablesHelper do ...@@ -19,27 +19,29 @@ RSpec.describe IssuablesHelper do
@group = epic.group @group = epic.group
expected_data = { expected_data = {
canAdmin: true,
canDestroy: true,
canUpdate: true,
confidential: epic.confidential,
endpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}", endpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}",
epicLinksEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}/links", epicLinksEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}/links",
updateEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}.json", epicsWebUrl: "/groups/#{@group.full_path}/-/epics",
issueLinksEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}/issues",
canUpdate: true,
canDestroy: true,
canAdmin: true,
issuableRef: "&#{epic.iid}",
markdownPreviewPath: "/groups/#{@group.full_path}/preview_markdown",
markdownDocsPath: '/help/user/markdown',
issuableTemplateNamesPath: '',
lockVersion: epic.lock_version,
fullPath: @group.full_path, fullPath: @group.full_path,
groupPath: @group.path, groupPath: @group.path,
initialTitleHtml: epic.title,
initialTitleText: epic.title,
initialDescriptionHtml: '<p data-sourcepos="1:1-1:9" dir="auto">epic text</p>', initialDescriptionHtml: '<p data-sourcepos="1:1-1:9" dir="auto">epic text</p>',
initialDescriptionText: 'epic text', initialDescriptionText: 'epic text',
initialTaskStatus: '0 of 0 tasks completed', initialTaskStatus: '0 of 0 tasks completed',
initialTitleHtml: epic.title,
initialTitleText: epic.title,
issuableRef: "&#{epic.iid}",
issuableTemplateNamesPath: '',
issueLinksEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}/issues",
issuesWebUrl: "/groups/#{@group.full_path}/-/issues",
lockVersion: epic.lock_version,
markdownDocsPath: '/help/user/markdown',
markdownPreviewPath: "/groups/#{@group.full_path}/preview_markdown",
projectsEndpoint: "/api/v4/groups/#{@group.id}/projects", projectsEndpoint: "/api/v4/groups/#{@group.id}/projects",
confidential: epic.confidential updateEndpoint: "/groups/#{@group.full_path}/-/epics/#{epic.iid}.json"
} }
expect(helper.issuable_initial_data(epic)).to eq(expected_data) expect(helper.issuable_initial_data(epic)).to eq(expected_data)
end end
......
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