Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
e2e0cb4f
Commit
e2e0cb4f
authored
Mar 30, 2022
by
Rajat Jain
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Roadmap App with Tree View
Embed the roadmap app with the epic tree view Changelog: changed EE: true
parent
ea08a31e
Changes
21
Hide whitespace changes
Inline
Side-by-side
Showing
21 changed files
with
533 additions
and
374 deletions
+533
-374
ee/app/assets/javascripts/epic/components/epic_app.vue
ee/app/assets/javascripts/epic/components/epic_app.vue
+3
-3
ee/app/assets/javascripts/epic/components/epic_tabs.vue
ee/app/assets/javascripts/epic/components/epic_tabs.vue
+0
-109
ee/app/assets/javascripts/related_items_tree/components/related_items_roadmap_app.vue
...lated_items_tree/components/related_items_roadmap_app.vue
+50
-0
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_actions.vue
...ated_items_tree/components/related_items_tree_actions.vue
+68
-0
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_app.vue
.../related_items_tree/components/related_items_tree_app.vue
+26
-4
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_header.vue
...lated_items_tree/components/related_items_tree_header.vue
+70
-62
ee/app/assets/javascripts/related_items_tree/components/tree_item.vue
...s/javascripts/related_items_tree/components/tree_item.vue
+1
-1
ee/app/assets/javascripts/related_items_tree/components/tree_item_body.vue
...ascripts/related_items_tree/components/tree_item_body.vue
+1
-1
ee/app/assets/javascripts/related_items_tree/components/tree_root.vue
...s/javascripts/related_items_tree/components/tree_root.vue
+1
-1
ee/app/assets/javascripts/related_items_tree/constants.js
ee/app/assets/javascripts/related_items_tree/constants.js
+5
-0
ee/app/assets/javascripts/related_items_tree/related_items_tree_bundle.js
...vascripts/related_items_tree/related_items_tree_bundle.js
+4
-0
ee/app/assets/stylesheets/components/related_items_tree.scss
ee/app/assets/stylesheets/components/related_items_tree.scss
+24
-3
ee/app/views/groups/epics/show.html.haml
ee/app/views/groups/epics/show.html.haml
+29
-34
ee/spec/features/epics/epic_show_spec.rb
ee/spec/features/epics/epic_show_spec.rb
+15
-16
ee/spec/frontend/epic/components/epic_tabs_spec.js
ee/spec/frontend/epic/components/epic_tabs_spec.js
+0
-124
ee/spec/frontend/related_items_tree/components/related_items_roadmap_app_spec.js
...d_items_tree/components/related_items_roadmap_app_spec.js
+83
-0
ee/spec/frontend/related_items_tree/components/related_items_tree_actions_spec.js
..._items_tree/components/related_items_tree_actions_spec.js
+91
-0
ee/spec/frontend/related_items_tree/components/related_items_tree_app_spec.js
...ated_items_tree/components/related_items_tree_app_spec.js
+38
-0
ee/spec/frontend/related_items_tree/components/related_items_tree_header_spec.js
...d_items_tree/components/related_items_tree_header_spec.js
+2
-13
ee/spec/frontend/related_items_tree/mock_data.js
ee/spec/frontend/related_items_tree/mock_data.js
+16
-0
locale/gitlab.pot
locale/gitlab.pot
+6
-3
No files found.
ee/app/assets/javascripts/epic/components/epic_app.vue
View file @
e2e0cb4f
<
script
>
import
initRelatedItemsTree
from
'
ee/related_items_tree/related_items_tree_bundle
'
;
import
SidebarContext
from
'
../sidebar_context
'
;
import
EpicBody
from
'
./epic_body.vue
'
;
import
EpicHeader
from
'
./epic_header.vue
'
;
import
EpicTabs
from
'
./epic_tabs.vue
'
;
export
default
{
components
:
{
EpicHeader
,
EpicBody
,
EpicTabs
,
},
mounted
()
{
this
.
sidebarContext
=
new
SidebarContext
();
initRelatedItemsTree
();
},
};
</
script
>
...
...
@@ -21,6 +22,5 @@ export default {
<div
class=
"epic-page-container"
>
<epic-header
/>
<epic-body
/>
<epic-tabs
/>
</div>
</
template
>
ee/app/assets/javascripts/epic/components/epic_tabs.vue
deleted
100644 → 0
View file @
ea08a31e
<
script
>
import
{
GlButtonGroup
,
GlButton
}
from
'
@gitlab/ui
'
;
import
initRelatedItemsTree
from
'
ee/related_items_tree/related_items_tree_bundle
'
;
const
displayNoneClass
=
'
gl-display-none
'
;
const
containerClass
=
'
container-limited
'
;
export
default
{
components
:
{
GlButton
,
GlButtonGroup
,
},
inject
:
{
allowSubEpics
:
{
default
:
false
,
},
treeElementSelector
:
{
default
:
null
,
},
roadmapElementSelector
:
{
default
:
null
,
},
containerElementSelector
:
{
default
:
null
,
},
},
data
()
{
return
{
roadmapLoaded
:
false
,
activeButton
:
this
.
$options
.
TABS
.
TREE
,
};
},
computed
:
{
shouldLoadRoadmap
()
{
return
!
this
.
roadmapLoaded
&&
this
.
allowSubEpics
;
},
},
mounted
()
{
initRelatedItemsTree
();
},
beforeMount
()
{
this
.
treeElement
=
document
.
querySelector
(
this
.
treeElementSelector
);
this
.
roadmapElement
=
document
.
querySelector
(
this
.
roadmapElementSelector
);
this
.
containerElement
=
document
.
querySelector
(
this
.
containerElementSelector
);
},
methods
:
{
initRoadmap
()
{
return
import
(
'
ee/roadmap/roadmap_bundle
'
)
.
then
((
roadmapBundle
)
=>
{
roadmapBundle
.
default
();
})
.
catch
(()
=>
{});
},
onTreeTabClick
()
{
this
.
activeButton
=
this
.
$options
.
TABS
.
TREE
;
this
.
roadmapElement
.
classList
.
add
(
displayNoneClass
);
this
.
treeElement
.
classList
.
remove
(
displayNoneClass
);
this
.
containerElement
.
classList
.
add
(
containerClass
);
},
showRoadmapTabContent
()
{
this
.
activeButton
=
this
.
$options
.
TABS
.
ROADMAP
;
this
.
roadmapElement
.
classList
.
remove
(
displayNoneClass
);
this
.
treeElement
.
classList
.
add
(
displayNoneClass
);
this
.
containerElement
.
classList
.
remove
(
containerClass
);
},
onRoadmapTabClick
()
{
if
(
this
.
shouldLoadRoadmap
)
{
this
.
initRoadmap
()
.
then
(()
=>
{
this
.
roadmapLoaded
=
true
;
this
.
showRoadmapTabContent
();
})
.
catch
(()
=>
{});
}
else
{
this
.
showRoadmapTabContent
();
}
},
},
TABS
:
{
TREE
:
'
related_items_tree
'
,
ROADMAP
:
'
roadmap
'
,
},
};
</
script
>
<
template
>
<div
class=
"epic-tabs-holder gl-pl-0 gl-pr-0 gl-ml-0 gl-mr-0"
>
<div
class=
"epic-tabs-container gl-pt-3 gl-pb-3"
>
<gl-button-group
data-testid=
"tabs"
>
<gl-button
class=
"js-epic-tree-tab"
data-testid=
"epic-tree-tab"
:selected=
"activeButton === $options.TABS.TREE"
@
click=
"onTreeTabClick"
>
{{
allowSubEpics
?
__
(
'
Epics and Issues
'
)
:
__
(
'
Issues
'
)
}}
</gl-button>
<gl-button
v-if=
"allowSubEpics"
class=
"js-epic-roadmap-tab"
data-testid=
"epic-roadmap-tab"
:selected=
"activeButton === $options.TABS.ROADMAP"
@
click=
"onRoadmapTabClick"
>
{{
__
(
'
Roadmap
'
)
}}
</gl-button>
</gl-button-group>
</div>
</div>
</
template
>
ee/app/assets/javascripts/related_items_tree/components/related_items_roadmap_app.vue
0 → 100644
View file @
e2e0cb4f
<
script
>
import
{
mapState
}
from
'
vuex
'
;
export
default
{
inject
:
[
'
roadmapAppData
'
],
computed
:
{
...
mapState
([
'
allowSubEpics
'
]),
roadmapAttrs
()
{
if
(
!
this
.
roadmapAppData
)
{
return
{};
}
return
Object
.
keys
(
this
.
roadmapAppData
).
reduce
((
acc
,
key
)
=>
{
const
hypenCasedKey
=
key
.
replace
(
/_/g
,
'
-
'
);
acc
[
`data-
${
hypenCasedKey
}
`
]
=
this
.
roadmapAppData
[
key
];
return
acc
;
},
{});
},
shouldLoadRoadmap
()
{
return
!
this
.
roadmapLoaded
&&
this
.
allowSubEpics
;
},
},
mounted
()
{
if
(
this
.
shouldLoadRoadmap
)
{
this
.
initRoadmap
()
.
then
(()
=>
{
this
.
roadmapLoaded
=
true
;
})
.
catch
(()
=>
{});
}
},
methods
:
{
initRoadmap
()
{
return
import
(
'
ee/roadmap/roadmap_bundle
'
)
.
then
((
roadmapBundle
)
=>
{
roadmapBundle
.
default
();
})
.
catch
(()
=>
{});
},
},
};
</
script
>
<
template
>
<div
class=
"gl-px-3 gl-py-3 gl-bg-gray-10"
>
<div
id=
"roadmap"
class=
"roadmap-app border gl-rounded-base gl-bg-white"
>
<div
id=
"js-roadmap"
v-bind=
"roadmapAttrs"
></div>
</div>
</div>
</
template
>
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_actions.vue
0 → 100644
View file @
e2e0cb4f
<
script
>
import
{
GlButtonGroup
,
GlButton
}
from
'
@gitlab/ui
'
;
import
{
mapState
}
from
'
vuex
'
;
import
{
ITEM_TABS
}
from
'
../constants
'
;
import
ToggleLabels
from
'
../../boards/components/toggle_labels.vue
'
;
export
default
{
ITEM_TABS
,
components
:
{
GlButtonGroup
,
GlButton
,
ToggleLabels
,
},
props
:
{
activeTab
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
...
mapState
([
'
allowSubEpics
'
]),
},
methods
:
{
onTreeTabClick
()
{
this
.
$emit
(
'
tab-change
'
,
this
.
$options
.
ITEM_TABS
.
TREE
);
},
onRoadmapTabClick
()
{
this
.
$emit
(
'
tab-change
'
,
this
.
$options
.
ITEM_TABS
.
ROADMAP
);
},
},
};
</
script
>
<
template
>
<div
class=
"card-header d-flex gl-px-5 gl-pt-4 gl-pt-3 flex-column flex-sm-row border-bottom-0"
>
<div>
<gl-button-group
data-testid=
"buttons"
class=
"gl-flex-grow-1 gl-display-flex"
>
<gl-button
class=
"js-epic-tree-tab"
data-testid=
"tree-view-button"
:selected=
"activeTab === $options.ITEM_TABS.TREE"
@
click=
"onTreeTabClick"
>
{{
__
(
'
Tree view
'
)
}}
</gl-button>
<gl-button
v-if=
"allowSubEpics"
class=
"js-epic-roadmap-tab"
data-testid=
"roadmap-view-button"
:selected=
"activeTab === $options.ITEM_TABS.ROADMAP"
@
click=
"onRoadmapTabClick"
>
{{
__
(
'
Roadmap view
'
)
}}
</gl-button>
</gl-button-group>
</div>
<div
class=
"ml-auto gl-display-none gl-sm-display-flex"
>
<!-- empty -->
</div>
<div
v-if=
"activeTab === $options.ITEM_TABS.TREE"
class=
"gl-sm-display-inline-flex gl-display-flex gl-mt-3 gl-sm-mt-0"
>
<toggle-labels
class=
"gl-sm-ml-3! gl-ml-0!"
/>
</div>
</div>
</
template
>
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_app.vue
View file @
e2e0cb4f
...
...
@@ -6,11 +6,13 @@ import { __, sprintf } from '~/locale';
import
AddItemForm
from
'
~/related_issues/components/add_issuable_form.vue
'
;
import
SlotSwitch
from
'
~/vue_shared/components/slot_switch.vue
'
;
import
{
issuableTypesMap
}
from
'
~/related_issues/constants
'
;
import
{
OVERFLOW_AFTER
}
from
'
../constants
'
;
import
{
ITEM_TABS
,
OVERFLOW_AFTER
}
from
'
../constants
'
;
import
CreateEpicForm
from
'
./create_epic_form.vue
'
;
import
CreateIssueForm
from
'
./create_issue_form.vue
'
;
import
RelatedItemsTreeBody
from
'
./related_items_tree_body.vue
'
;
import
RelatedItemsTreeHeader
from
'
./related_items_tree_header.vue
'
;
import
RelatedItemsTreeActions
from
'
./related_items_tree_actions.vue
'
;
import
RelatedItemsRoadmapApp
from
'
./related_items_roadmap_app.vue
'
;
import
TreeItemRemoveModal
from
'
./tree_item_remove_modal.vue
'
;
const
FORM_SLOTS
=
{
...
...
@@ -22,11 +24,14 @@ const FORM_SLOTS = {
export
default
{
OVERFLOW_AFTER
,
FORM_SLOTS
,
ITEM_TABS
,
components
:
{
GlLoadingIcon
,
GlIcon
,
RelatedItemsTreeHeader
,
RelatedItemsTreeBody
,
RelatedItemsTreeActions
,
RelatedItemsRoadmapApp
,
AddItemForm
,
CreateEpicForm
,
TreeItemRemoveModal
,
...
...
@@ -36,6 +41,11 @@ export default {
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
data
()
{
return
{
activeTab
:
ITEM_TABS
.
TREE
,
};
},
computed
:
{
...
mapState
([
'
parentItem
'
,
...
...
@@ -157,24 +167,28 @@ export default {
this
.
toggleCreateEpicForm
({
toggleState
:
false
});
this
.
setItemInputValue
(
''
);
},
handleTabChange
(
value
)
{
this
.
activeTab
=
value
;
},
},
};
</
script
>
<
template
>
<div
class=
"related-items-tree-container"
>
<div
class=
"related-items-tree-container
gl-mt-5
"
>
<div
v-if=
"itemsFetchInProgress"
class=
"mt-2"
>
<gl-loading-icon
size=
"md"
/>
</div>
<div
v-else
class=
"related-items-tree card card-slim
border-top-0
"
class=
"related-items-tree card card-slim"
:class=
"
{
'disabled-content': disableContents,
'overflow-auto': directChildren.length > $options.OVERFLOW_AFTER,
}"
>
<related-items-tree-header
:class=
"
{ 'border-bottom-0': itemsFetchResultEmpty }" />
<slot-switch
v-if=
"visibleForm && parentItem.confidential"
:active-slot-names=
"[visibleForm]"
...
...
@@ -240,11 +254,19 @@ export default {
/>
</
template
>
</slot-switch>
<related-items-tree-body
<related-items-tree-actions
v-if=
"!itemsFetchResultEmpty"
:active-tab=
"activeTab"
@
tab-change=
"handleTabChange"
/>
<related-items-tree-body
v-if=
"!itemsFetchResultEmpty && activeTab === $options.ITEM_TABS.TREE"
:parent-item=
"parentItem"
:children=
"directChildren"
/>
<related-items-roadmap-app
v-if=
"activeTab === $options.ITEM_TABS.ROADMAP"
/>
<tree-item-remove-modal
/>
</div>
</div>
...
...
ee/app/assets/javascripts/related_items_tree/components/related_items_tree_header.vue
View file @
e2e0cb4f
...
...
@@ -3,8 +3,6 @@ import { GlTooltip, GlIcon } from '@gitlab/ui';
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
{
issuableTypesMap
}
from
'
~/related_issues/constants
'
;
import
ToggleLabels
from
'
../../boards/components/toggle_labels.vue
'
;
import
EpicHealthStatus
from
'
./epic_health_status.vue
'
;
import
EpicActionsSplitButton
from
'
./epic_issue_actions_split_button.vue
'
;
...
...
@@ -14,7 +12,6 @@ export default {
GlIcon
,
EpicHealthStatus
,
EpicActionsSplitButton
,
ToggleLabels
,
},
computed
:
{
...
mapState
([
...
...
@@ -73,72 +70,83 @@ export default {
</
script
>
<
template
>
<div
class=
"card-header d-flex px-2 flex-column flex-sm-row"
>
<div
class=
"d-inline-flex flex-grow-1 lh-100 align-middle mb-2 mb-sm-0"
>
<gl-tooltip
:target=
"() => $refs.countBadge"
>
<p
v-if=
"allowSubEpics"
class=
"font-weight-bold m-0"
>
{{
__
(
'
Epics
'
)
}}
•
<span
class=
"font-weight-normal"
>
{{
sprintf
(
__
(
'
%{openedEpics
}
open, %{closedEpics
}
closed
'
),
{
openedEpics
:
descendantCounts
.
openedEpics
,
closedEpics
:
descendantCounts
.
closedEpics
,
}
)
}}
<
/span
>
<
/p
>
<
p
class
=
"
font-weight-bold m-0
"
>
{{
__
(
'
Issues
'
)
}}
&
#
8226
;
<
span
class
=
"
font-weight-normal
"
>
{{
sprintf
(
__
(
'
%{openedIssues
}
open, %{closedIssues
}
closed
'
),
{
openedIssues
:
descendantCounts
.
openedIssues
,
closedIssues
:
descendantCounts
.
closedIssues
,
}
)
}}
<
/span
>
<
/p
>
<
p
class
=
"
font-weight-bold m-0
"
>
{{
__
(
'
Total weight
'
)
}}
&
#
8226
;
<
span
class
=
"
font-weight-normal
"
>
{{
totalWeight
}}
<
/span
>
<
/p
>
<
/gl-tooltip
>
<div
class=
"card-header d-flex gl-px-5 gl-py-3 flex-column flex-sm-row"
>
<div
class=
"flex flex-grow-1 flex-shrink-0 gl-flex-wrap flex-column flex-sm-row"
>
<div
class=
"flex flex-shrink-0 align-items-center gl-flex-wrap"
>
<h3
class=
"card-title h5 gl-my-0 flex-shrink-0"
>
{{
allowSubEpics
?
__
(
'
Child issues and epics
'
)
:
__
(
'
Issues
'
)
}}
</h3>
<div
class=
"d-inline-flex lh-100 align-middle gl-ml-5 gl-flex-wrap"
>
<gl-tooltip
:target=
"() => $refs.countBadge"
>
<p
v-if=
"allowSubEpics"
class=
"font-weight-bold m-0"
>
{{
__
(
'
Epics
'
)
}}
•
<span
class=
"font-weight-normal"
>
{{
sprintf
(
__
(
'
%{openedEpics
}
open, %{closedEpics
}
closed
'
),
{
openedEpics
:
descendantCounts
.
openedEpics
,
closedEpics
:
descendantCounts
.
closedEpics
,
}
)
}}
<
/span
>
<
/p
>
<
p
class
=
"
font-weight-bold m-0
"
>
{{
__
(
'
Issues
'
)
}}
&
#
8226
;
<
span
class
=
"
font-weight-normal
"
>
{{
sprintf
(
__
(
'
%{openedIssues
}
open, %{closedIssues
}
closed
'
),
{
openedIssues
:
descendantCounts
.
openedIssues
,
closedIssues
:
descendantCounts
.
closedIssues
,
}
)
}}
<
/span
>
<
/p
>
<
p
class
=
"
font-weight-bold m-0
"
>
{{
__
(
'
Total weight
'
)
}}
&
#
8226
;
<
span
class
=
"
font-weight-normal
"
>
{{
totalWeight
}}
<
/span
>
<
/p
>
<
/gl-tooltip
>
<
div
ref
=
"
countBadge
"
class
=
"
issue-count-badge gl-display-inline-flex text-secondary p-0 pr-3
"
>
<
span
v
-
if
=
"
allowSubEpics
"
class
=
"
d-inline-flex align-items-center
"
>
<
gl
-
icon
name
=
"
epic
"
class
=
"
mr-1
"
/>
{{
totalEpicsCount
}}
<
/span
>
<
span
class
=
"
d-inline-flex align-items-center
"
:
class
=
"
{ 'gl-ml-3': allowSubEpics
}
"
>
<
gl
-
icon
name
=
"
issues
"
class
=
"
mr-1
"
/>
{{
totalIssuesCount
}}
<
/span
>
<
span
class
=
"
d-inline-flex align-items-center
"
:
class
=
"
{ 'gl-ml-3': allowSubEpics
}
"
>
<
gl
-
icon
name
=
"
weight
"
class
=
"
mr-1
"
/>
{{
totalWeight
}}
<
/span
>
<
/div
>
<
/div
>
<
/div
>
<
div
ref
=
"
countBadge
"
class
=
"
issue-count-badge gl-display-inline-flex text-secondary p-0 pr-3
"
class
=
"
gl-display-flex gl-sm-display-inline-flex lh-100 align-middle gl-sm-ml-2 gl-ml-0 gl-flex-wrap gl-mt-2 gl-sm-mt-0
"
>
<
span
v
-
if
=
"
allowSubEpics
"
class
=
"
d-inline-flex align-items-center
"
>
<
gl
-
icon
name
=
"
epic
"
class
=
"
mr-1
"
/>
{{
totalEpicsCount
}}
<
/span
>
<
span
class
=
"
d-inline-flex align-items-center
"
:
class
=
"
{ 'ml-3': allowSubEpics
}
"
>
<
gl
-
icon
name
=
"
issues
"
class
=
"
mr-1
"
/>
{{
totalIssuesCount
}}
<
/span
>
<
span
class
=
"
d-inline-flex align-items-center
"
:
class
=
"
{ 'ml-3': allowSubEpics
}
"
>
<
gl
-
icon
name
=
"
weight
"
class
=
"
mr-1
"
/>
{{
totalWeight
}}
<
/span
>
<
epic
-
health
-
status
v
-
if
=
"
showHealthStatus
"
:
health
-
status
=
"
healthStatus
"
/>
<
/div
>
<
epic
-
health
-
status
v
-
if
=
"
showHealthStatus
"
:
health
-
status
=
"
healthStatus
"
/>
<
/div
>
<
div
class
=
"
gl-display-inline-flex gl-mr-3
"
>
<
toggle
-
labels
/>
<
/div
>
<
div
v
-
if
=
"
parentItem.userPermissions.adminEpic
"
class
=
"
d-inline-flex flex-column flex-sm-row js-button-container
"
class
=
"
gl-display-flex gl-sm-display-inline-flex gl-sm-ml-auto lh-100 align-middle gl-mt-3 gl-sm-mt-0 gl-pl-0 gl-sm-pl-7
"
>
<
epic
-
actions
-
split
-
button
:
allow
-
sub
-
epics
=
"
allowSubEpics
"
class
=
"
js-add-epics-issues-button qa-add-epics-button mb-2 mb-sm-0
"
@
showAddIssueForm
=
"
showAddIssueForm
"
@
showCreateIssueForm
=
"
showCreateIssueForm
"
@
showAddEpicForm
=
"
showAddEpicForm
"
@
showCreateEpicForm
=
"
showCreateEpicForm
"
/>
<
div
v
-
if
=
"
parentItem.userPermissions.adminEpic
"
class
=
"
gl-flex-grow-1 flex-column flex-sm-row js-button-container
"
>
<
epic
-
actions
-
split
-
button
:
allow
-
sub
-
epics
=
"
allowSubEpics
"
class
=
"
js-add-epics-issues-button qa-add-epics-button w-100
"
@
showAddIssueForm
=
"
showAddIssueForm
"
@
showCreateIssueForm
=
"
showCreateIssueForm
"
@
showAddEpicForm
=
"
showAddEpicForm
"
@
showCreateEpicForm
=
"
showCreateEpicForm
"
/>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
ee/app/assets/javascripts/related_items_tree/components/tree_item.vue
View file @
e2e0cb4f
...
...
@@ -97,7 +97,7 @@ export default {
</gl-button>
<gl-loading-icon
v-if=
"childrenFetchInProgress"
class=
"loading-icon"
size=
"sm"
/>
<tree-item-body
class=
"tree-item-row"
class=
"tree-item-row
gl-mb-3
"
:parent-item=
"parentItem"
:item=
"item"
:class=
"
{
...
...
ee/app/assets/javascripts/related_items_tree/components/tree_item_body.vue
View file @
e2e0cb4f
...
...
@@ -313,7 +313,7 @@ export default {
<
item
-
assignees
v
-
if
=
"
hasAssignees
"
:
assignees
=
"
item.assignees
"
class
=
"
item-assignees gl-display-inline-flex gl-align-items-center gl-mr-5
mb-md-0
flex-xl-grow-0
"
class
=
"
item-assignees gl-display-inline-flex gl-align-items-center gl-mr-5
gl-mb-3
flex-xl-grow-0
"
/>
<
epic
-
health
-
status
...
...
ee/app/assets/javascripts/related_items_tree/components/tree_root.vue
View file @
e2e0cb4f
...
...
@@ -87,7 +87,7 @@ export default {
<component
:is=
"treeRootWrapper"
v-bind=
"treeRootOptions"
class=
"list-unstyled related-items-list tree-root"
class=
"list-unstyled related-items-list tree-root
gl-px-3 gl-py-3
"
:move=
"onMove"
@
start=
"handleDragOnStart"
@
end=
"handleDragOnEnd"
...
...
ee/app/assets/javascripts/related_items_tree/constants.js
View file @
e2e0cb4f
...
...
@@ -60,3 +60,8 @@ export const issueHealthStatusCSSMapping = {
};
export
const
trackingAddedIssue
=
'
g_project_management_users_epic_issue_added_from_epic
'
;
export
const
ITEM_TABS
=
{
TREE
:
'
tree
'
,
ROADMAP
:
'
roadmap
'
,
};
ee/app/assets/javascripts/related_items_tree/related_items_tree_bundle.js
View file @
e2e0cb4f
...
...
@@ -32,6 +32,7 @@ export default () => {
allowSubEpics
,
}
=
el
.
dataset
;
const
initialData
=
JSON
.
parse
(
el
.
dataset
.
initial
);
const
roadmapAppData
=
JSON
.
parse
(
el
.
dataset
.
roadmapAppData
);
Vue
.
component
(
'
TreeRoot
'
,
TreeRoot
);
Vue
.
component
(
'
TreeItem
'
,
TreeItem
);
...
...
@@ -41,6 +42,9 @@ export default () => {
name
:
'
RelatedItemsTreeRoot
'
,
store
:
createStore
(),
components
:
{
RelatedItemsTreeApp
},
provide
:
{
roadmapAppData
,
},
created
()
{
this
.
setInitialParentItem
({
fullPath
,
...
...
ee/app/assets/stylesheets/components/related_items_tree.scss
View file @
e2e0cb4f
.related-items-tree
{
border-top-left-radius
:
0
;
border-top-right-radius
:
0
;
.add-item-form-container
{
border-bottom
:
1px
solid
$border-color
;
...
...
@@ -65,6 +62,9 @@
}
.related-items-tree-body
{
border-bottom-left-radius
:
$gl-border-radius-base
;
border-bottom-right-radius
:
$gl-border-radius-base
;
>
.tree-root
{
padding-top
:
$gl-vert-padding
;
padding-bottom
:
0
;
...
...
@@ -81,3 +81,24 @@
margin-bottom
:
$gl-vert-padding
;
}
}
.related-items-tree-container
{
.roadmap-app-container
{
.js-roadmap-shell
{
border-radius
:
$gl-border-radius-base
;
}
.epics-list-item-empty
{
display
:
none
;
}
// This is a hacky CSS to remove the border-bottom from the
// last list in the roadmap.
.epic-item-container
:nth-last-child
(
4
)
{
.epic-details-cell
,
.epic-timeline-cell
{
border-bottom
:
0
;
}
}
}
}
ee/app/views/groups/epics/show.html.haml
View file @
e2e0cb4f
...
...
@@ -37,42 +37,37 @@
'data-roadmap-element-selector'
=>
"##{roadmapElementID}"
,
'data-container-element-selector'
=>
".#{containerClass}"
}
.epic-tabs-content.js-epic-tabs-content
%div
{
id:
treeElementID
,
class:
[
'tab-pane'
,
'show'
,
'active'
]
}
.row
%section
.col-md-12
#js-tree
{
data:
{
id:
@epic
.
to_global_id
,
numerical_id:
@epic
.
id
,
iid:
@epic
.
iid
,
group_name:
@group
.
name
,
%div
{
id:
treeElementID
,
class:
[
'tab-pane'
,
'show'
,
'active'
]
}
.row
%section
.col-md-12
#js-tree
{
data:
{
id:
@epic
.
to_global_id
,
numerical_id:
@epic
.
id
,
iid:
@epic
.
iid
,
group_name:
@group
.
name
,
group_id:
@group
.
id
,
full_path:
@group
.
full_path
,
auto_complete_epics:
allow_sub_epics
,
auto_complete_issues:
'true'
,
user_signed_in:
current_user
.
present?
?
'true'
:
'false'
,
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
,
roadmap_app_data:
sub_epics_feature_available
?
{
epics_path:
group_epics_path
(
@group
,
parent_id:
@epic
.
id
,
format:
:
json
),
group_id:
@group
.
id
,
iid:
@epic
.
iid
,
full_path:
@group
.
full_path
,
auto_complete_epics:
allow_sub_epics
,
auto_complete_issues:
'true'
,
user_signed_in:
current_user
.
present?
?
'true'
:
'false'
,
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
}
}
-
if
sub_epics_feature_available
%div
{
id:
roadmapElementID
,
class:
[
'tab-pane'
,
'gl-display-none'
]
}
.row
%section
.col-md-12
#js-roadmap
{
data:
{
epics_path:
group_epics_path
(
@group
,
parent_id:
@epic
.
id
,
format: :json
),
group_id:
@group
.
id
,
iid:
@epic
.
iid
,
full_path:
@group
.
full_path
,
empty_state_illustration:
image_path
(
'illustrations/epics/roadmap.svg'
),
has_filters_applied:
'false'
,
new_epic_path:
new_group_epic_path
(
@group
),
list_epics_path:
group_epics_path
(
@group
),
epics_docs_path:
help_page_path
(
'user/group/epics/index'
),
preset_type:
roadmap_layout
,
epics_state:
'all'
,
sorted_by:
roadmap_sort_order
,
inner_height:
'600'
,
child_epics:
'true'
}
}
empty_state_illustration:
image_path
(
'illustrations/epics/roadmap.svg'
),
has_filters_applied:
false
,
new_epic_path:
new_group_epic_path
(
@group
),
list_epics_path:
group_epics_path
(
@group
),
epics_docs_path:
help_page_path
(
'user/group/epics/index'
),
preset_type:
roadmap_layout
,
epics_state:
'all'
,
sorted_by:
roadmap_sort_order
,
inner_height:
'600'
,
child_epics:
true
}.
to_json
:
'null'
}
}
-
if
related_epics_feature_available
&&
Feature
.
enabled?
(
:related_epics_widget
,
@group
,
default_enabled: :yaml
)
#js-related-epics
{
data:
{
endpoint:
group_epic_related_epic_links_path
(
@group
,
@epic
),
can_add_related_epics:
"#{can?(current_user, :admin_related_epic_link, @epic)}"
,
...
...
ee/spec/features/epics/epic_show_spec.rb
View file @
e2e0cb4f
...
...
@@ -38,7 +38,7 @@ RSpec.describe 'Epic show', :js do
button_name
=
type
==
'issue'
?
'Add an existing issue'
:
'Add an existing epic'
input_character
=
type
==
'issue'
?
'#'
:
'&'
page
.
within
(
'.
js-epic-tabs-content #tree
'
)
do
page
.
within
(
'.
related-items-tree-container
'
)
do
find
(
'.js-add-epics-issues-button .dropdown-toggle'
).
click
click_button
button_name
fill_in
"Paste
#{
type
}
link"
,
with:
input_character
...
...
@@ -52,15 +52,15 @@ RSpec.describe 'Epic show', :js do
end
describe
'Epic metadata'
do
it
'shows
epic tabs `Epics and Issues` and `Roadmap
`'
do
expect
(
find
(
'
.js-epic-tree-tab'
)).
to
have_content
(
'Epics and Issues
'
)
expect
(
find
(
'
.js-epic-roadmap-tab'
)).
to
have_content
(
'Roadmap
'
)
it
'shows
buttons `Tree view` and `Roadmap view
`'
do
expect
(
find
(
'
[data-testid="tree-view-button"]'
)).
to
have_content
(
'Tree view
'
)
expect
(
find
(
'
[data-testid="roadmap-view-button"]'
)).
to
have_content
(
'Roadmap view
'
)
end
end
describe
'Epics and Issues tab'
do
it
'shows Related items tree with child epics'
do
page
.
within
(
'.js-epic-
tabs-content #tree
'
)
do
page
.
within
(
'.js-epic-
container
'
)
do
expect
(
page
).
to
have_selector
(
'.related-items-tree-container'
)
page
.
within
(
'.related-items-tree-container'
)
do
...
...
@@ -92,12 +92,12 @@ RSpec.describe 'Epic show', :js do
describe
'Roadmap tab'
do
before
do
find
(
'
.js-epic-roadmap-tab
'
).
click
find
(
'
[data-testid="roadmap-view-button"]
'
).
click
wait_for_requests
end
it
'shows Roadmap timeline with child epics'
,
quarantine:
'https://gitlab.com/gitlab-org/gitlab/-/issues/299298'
do
page
.
within
(
'.
js-epic-tabs-content
#roadmap'
)
do
page
.
within
(
'.
related-items-tree-container
#roadmap'
)
do
expect
(
page
).
to
have_selector
(
'.roadmap-container .js-roadmap-shell'
)
page
.
within
(
'.js-roadmap-shell .epics-list-section'
)
do
...
...
@@ -121,14 +121,14 @@ RSpec.describe 'Epic show', :js do
find
(
'.js-epic-roadmap-tab'
).
click
wait_for_all_requests
# Wait for Roadmap bundle load and then Epics fetch load
page
.
within
(
'.
js-epic-tabs-content
'
)
do
page
.
within
(
'.
related-items-tree-container
'
)
do
expect
(
page
).
to
have_selector
(
'#roadmap.tab-pane'
,
visible:
true
)
expect
(
page
).
to
have_selector
(
'#tree.tab-pane'
,
visible:
false
)
end
find
(
'.js-epic-tree-tab'
).
click
page
.
within
(
'.
js-epic-tabs-content
'
)
do
page
.
within
(
'.
related-items-tree-container
'
)
do
expect
(
page
).
to
have_selector
(
'#tree.tab-pane'
,
visible:
true
)
expect
(
page
).
to
have_selector
(
'#roadmap.tab-pane'
,
visible:
false
)
end
...
...
@@ -137,23 +137,22 @@ RSpec.describe 'Epic show', :js do
describe
'when the sub-epics feature is not available'
do
before
do
stub_licensed_features
(
epics:
true
,
subepics:
false
)
visit
group_epic_path
(
group
,
epic
)
end
describe
'Epic metadata'
do
it
'shows epic tab `Issues`'
do
expect
(
find
(
'.js-epic-tree-tab'
)).
to
have_content
(
'Issues'
)
page
.
within
(
'.related-items-tree-container'
)
do
expect
(
find
(
'h3.card-title'
)).
to
have_content
(
'Issues'
)
end
end
end
describe
'Issues tab'
do
it
'shows Related items tree with child epics'
do
page
.
within
(
'.js-epic-tabs-content #tree'
)
do
expect
(
page
).
to
have_selector
(
'.related-items-tree-container'
)
page
.
within
(
'.related-items-tree-container'
)
do
expect
(
page
.
find
(
'.issue-count-badge'
,
text:
'1'
)).
to
be_present
end
page
.
within
(
'.related-items-tree-container'
)
do
expect
(
page
.
find
(
'.issue-count-badge'
,
text:
'1'
)).
to
be_present
end
end
end
...
...
ee/spec/frontend/epic/components/epic_tabs_spec.js
deleted
100644 → 0
View file @
ea08a31e
import
{
GlTab
}
from
'
@gitlab/ui
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
EpicTabs
from
'
ee/epic/components/epic_tabs.vue
'
;
import
waitForPromises
from
'
helpers/wait_for_promises
'
;
const
treeTabpaneID
=
'
tree
'
;
const
roadmapTabpaneID
=
'
roadmap
'
;
const
containerSelector
=
'
js-epic-container
'
;
const
displayNoneClass
=
'
gl-display-none
'
;
const
containerClass
=
'
container-limited
'
;
describe
(
'
EpicTabs
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
({
provide
=
{}
}
=
{})
=>
{
return
shallowMountExtended
(
EpicTabs
,
{
provide
:
{
treeElementSelector
:
`#
${
treeTabpaneID
}
`
,
roadmapElementSelector
:
`#
${
roadmapTabpaneID
}
`
,
containerElementSelector
:
`.
${
containerSelector
}
`
,
...
provide
,
},
stubs
:
{
GlTab
,
},
});
};
const
findEpicTreeTab
=
()
=>
wrapper
.
findByTestId
(
'
epic-tree-tab
'
);
const
findEpicRoadmapTab
=
()
=>
wrapper
.
findByTestId
(
'
epic-roadmap-tab
'
);
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
default bahviour
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
it
(
'
displays the tabs component
'
,
()
=>
{
expect
(
wrapper
.
findByTestId
(
'
tabs
'
).
exists
()).
toBe
(
true
);
});
it
(
'
displays the tree tab
'
,
()
=>
{
const
treeTab
=
findEpicTreeTab
();
expect
(
treeTab
.
exists
()).
toBe
(
true
);
expect
(
treeTab
.
text
()).
toBe
(
'
Issues
'
);
});
it
(
'
does not display the roadmap tab
'
,
()
=>
{
expect
(
findEpicRoadmapTab
().
exists
()).
toBe
(
false
);
});
});
describe
(
'
allowSubEpics = true
'
,
()
=>
{
it
(
'
displays the correct tree tab text
'
,
()
=>
{
wrapper
=
createComponent
({
provide
:
{
allowSubEpics
:
true
}
});
const
treeTab
=
findEpicTreeTab
();
expect
(
treeTab
.
exists
()).
toBe
(
true
);
expect
(
treeTab
.
text
()).
toBe
(
'
Epics and Issues
'
);
expect
(
treeTab
.
props
().
selected
).
toBe
(
true
);
});
it
(
'
displays the roadmap tab
'
,
()
=>
{
wrapper
=
createComponent
({
provide
:
{
allowSubEpics
:
true
}
});
const
treeTab
=
findEpicRoadmapTab
();
expect
(
treeTab
.
exists
()).
toBe
(
true
);
expect
(
treeTab
.
text
()).
toBe
(
'
Roadmap
'
);
expect
(
treeTab
.
props
().
selected
).
toBe
(
false
);
});
const
treeTabFixture
=
`
<div class="
${
containerSelector
}
">
<div id="
${
treeTabpaneID
}
" class="
${
displayNoneClass
}
"></div>
<div id="
${
roadmapTabpaneID
}
"></div>
</div>
`
;
const
roadmapFixture
=
`
<div class="
${
containerSelector
}
${
containerClass
}
">
<div id="
${
treeTabpaneID
}
"></div>
<div id="
${
roadmapTabpaneID
}
" class="
${
displayNoneClass
}
"></div>
</div>
`
;
const
treeExamples
=
[
[
'
hides the roadmap tab content
'
,
`#
${
roadmapTabpaneID
}
`
,
false
,
displayNoneClass
],
[
'
displays the tree tab content
'
,
`#
${
treeTabpaneID
}
`
,
true
,
displayNoneClass
],
[
'
sets the container to limtied width
'
,
`.
${
containerSelector
}
`
,
false
,
containerClass
],
];
const
roadmapExamples
=
[
[
'
hides the tree tab content
'
,
`#
${
treeTabpaneID
}
`
,
false
,
displayNoneClass
],
[
'
displays the roadmap tab content
'
,
`#
${
roadmapTabpaneID
}
`
,
true
,
displayNoneClass
],
[
'
removes the container width
'
,
`.
${
containerSelector
}
`
,
true
,
containerClass
],
];
describe
.
each
`
targetTab | tabTestId | fixture | examples
${
treeTabpaneID
}
|
${
'
epic-tree-tab
'
}
|
${
treeTabFixture
}
|
${
treeExamples
}
${
roadmapTabpaneID
}
|
${
'
epic-roadmap-tab
'
}
|
${
roadmapFixture
}
|
${
roadmapExamples
}
`
(
'
on $targetTab tab click
'
,
({
tabTestId
,
fixture
,
examples
})
=>
{
beforeEach
(()
=>
{
setFixtures
(
fixture
);
wrapper
=
createComponent
({
provide
:
{
allowSubEpics
:
true
}
});
});
it
.
each
(
examples
)(
'
%s
'
,
async
(
description
,
tabPaneSelector
,
hasClassName
,
className
)
=>
{
const
element
=
document
.
querySelector
(
tabPaneSelector
);
expect
(
element
.
classList
.
contains
(
className
)).
toBe
(
hasClassName
);
wrapper
.
findByTestId
(
tabTestId
).
vm
.
$emit
(
'
click
'
);
await
waitForPromises
();
expect
(
element
.
classList
.
contains
(
className
)).
not
.
toBe
(
hasClassName
);
});
});
});
});
ee/spec/frontend/related_items_tree/components/related_items_roadmap_app_spec.js
0 → 100644
View file @
e2e0cb4f
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
createDefaultStore
from
'
ee/related_items_tree/store
'
;
import
RelatedItemsRoadmapApp
from
'
ee/related_items_tree/components/related_items_roadmap_app.vue
'
;
import
{
mockInitialConfig
,
mockRoadmapAppData
}
from
'
../mock_data
'
;
Vue
.
use
(
Vuex
);
const
createComponent
=
({
initialConfig
=
{}
}
=
{})
=>
{
const
store
=
createDefaultStore
();
store
.
dispatch
(
'
setInitialConfig
'
,
{
...
mockInitialConfig
,
...
initialConfig
});
return
shallowMountExtended
(
RelatedItemsRoadmapApp
,
{
store
,
provide
:
{
roadmapAppData
:
mockRoadmapAppData
,
},
});
};
describe
(
'
RelatedItemsTree
'
,
()
=>
{
describe
(
'
RelatedItemsRoadmapApp
'
,
()
=>
{
describe
(
'
template
'
,
()
=>
{
let
wrapper
=
null
;
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
renders html
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
#roadmap
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
#js-roadmap
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders data-* attrs
'
,
()
=>
{
const
el
=
wrapper
.
find
(
'
#js-roadmap
'
);
const
normalizedData
=
Object
.
keys
(
mockRoadmapAppData
).
reduce
((
acc
,
key
)
=>
{
const
hypenCasedKey
=
key
.
replace
(
/_/g
,
'
-
'
);
acc
[
`data-
${
hypenCasedKey
}
`
]
=
mockRoadmapAppData
[
key
];
return
acc
;
},
{});
Object
.
keys
(
normalizedData
).
forEach
((
key
)
=>
{
expect
(
el
.
attributes
()[
key
]).
toBe
(
normalizedData
[
key
]);
});
});
});
describe
(
'
initRoadmap
'
,
()
=>
{
let
wrapper
=
null
;
let
initRoadmap
=
null
;
beforeEach
(()
=>
{
initRoadmap
=
jest
.
spyOn
(
RelatedItemsRoadmapApp
.
methods
,
'
initRoadmap
'
)
.
mockReturnValue
(
Promise
.
resolve
());
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
it
(
'
does not load roadmap
'
,
()
=>
{
wrapper
=
createComponent
({
initialConfig
:
{
allowSubEpics
:
false
,
},
});
expect
(
initRoadmap
).
not
.
toHaveBeenCalled
();
});
it
(
'
loads roadmap
'
,
()
=>
{
wrapper
=
createComponent
({});
expect
(
initRoadmap
).
toHaveBeenCalled
();
});
});
});
});
ee/spec/frontend/related_items_tree/components/related_items_tree_actions_spec.js
0 → 100644
View file @
e2e0cb4f
import
Vue
,
{
nextTick
}
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
{
shallowMountExtended
}
from
'
helpers/vue_test_utils_helper
'
;
import
ToggleLabels
from
'
ee/boards/components/toggle_labels.vue
'
;
import
RelatedItemsTreeActions
from
'
ee/related_items_tree/components/related_items_tree_actions.vue
'
;
import
{
ITEM_TABS
}
from
'
ee/related_items_tree/constants
'
;
import
createDefaultStore
from
'
ee/related_items_tree/store
'
;
import
{
mockInitialConfig
}
from
'
../mock_data
'
;
Vue
.
use
(
Vuex
);
const
createComponent
=
({
slots
}
=
{})
=>
{
const
store
=
createDefaultStore
();
store
.
dispatch
(
'
setInitialConfig
'
,
mockInitialConfig
);
return
shallowMountExtended
(
RelatedItemsTreeActions
,
{
store
,
slots
,
propsData
:
{
activeTab
:
ITEM_TABS
.
TREE
,
},
});
};
describe
(
'
RelatedItemsTree
'
,
()
=>
{
describe
(
'
RelatedItemsTreeActions
'
,
()
=>
{
let
wrapper
;
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
template
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
it
(
'
renders button group, tree view and roadmap view buttons
'
,
()
=>
{
const
buttonGroupEl
=
wrapper
.
findByTestId
(
'
buttons
'
);
const
treeViewEl
=
wrapper
.
findByTestId
(
'
tree-view-button
'
);
const
roadmapViewEl
=
wrapper
.
findByTestId
(
'
roadmap-view-button
'
);
expect
(
buttonGroupEl
.
isVisible
()).
toBe
(
true
);
expect
(
treeViewEl
.
isVisible
()).
toBe
(
true
);
expect
(
roadmapViewEl
.
isVisible
()).
toBe
(
true
);
});
it
(
'
does not render roadmap view button when subEpics are not present
'
,
async
()
=>
{
wrapper
.
vm
.
$store
.
dispatch
(
'
setInitialConfig
'
,
{
...
mockInitialConfig
,
allowSubEpics
:
false
,
});
await
nextTick
();
const
roadmapViewEl
=
wrapper
.
findByTestId
(
'
roadmap-view-button
'
);
expect
(
roadmapViewEl
.
exists
()).
toBe
(
false
);
});
describe
(
'
ToggleLabels
'
,
()
=>
{
it
(
'
renders when view is tree
'
,
()
=>
{
expect
(
wrapper
.
find
(
ToggleLabels
).
exists
()).
toBe
(
true
);
});
it
(
'
does not render when view is roadmap
'
,
async
()
=>
{
await
wrapper
.
setProps
({
activeTab
:
ITEM_TABS
.
ROADMAP
});
expect
(
wrapper
.
find
(
ToggleLabels
).
exists
()).
toBe
(
false
);
});
});
});
describe
(
'
emit tab-change
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
it
.
each
`
viewName | testid | name
${
'
tree view
'
}
|
${
'
tree-view-button
'
}
|
${
ITEM_TABS
.
TREE
}
${
'
roadmap view
'
}
|
${
'
roadmap-view-button
'
}
|
${
ITEM_TABS
.
ROADMAP
}
`
(
'
emits tab-change event when $viewName button is clicked
'
,
({
testid
,
name
})
=>
{
const
button
=
wrapper
.
findByTestId
(
testid
);
button
.
vm
.
$emit
(
'
click
'
);
expect
(
wrapper
.
emitted
(
'
tab-change
'
)[
0
]).
toEqual
([
name
]);
});
});
});
});
ee/spec/frontend/related_items_tree/components/related_items_tree_app_spec.js
View file @
e2e0cb4f
...
...
@@ -10,8 +10,13 @@ import AddIssuableForm from '~/related_issues/components/add_issuable_form.vue';
import
SlotSwitch
from
'
~/vue_shared/components/slot_switch.vue
'
;
import
RelatedItemsTreeApp
from
'
ee/related_items_tree/components/related_items_tree_app.vue
'
;
import
RelatedItemsTreeHeader
from
'
ee/related_items_tree/components/related_items_tree_header.vue
'
;
import
RelatedItemsTreeActions
from
'
ee/related_items_tree/components/related_items_tree_actions.vue
'
;
import
RelatedItemsTreeBody
from
'
ee/related_items_tree/components/related_items_tree_body.vue
'
;
import
RelatedItemsRoadmapApp
from
'
ee/related_items_tree/components/related_items_roadmap_app.vue
'
;
import
createDefaultStore
from
'
ee/related_items_tree/store
'
;
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
{
ITEM_TABS
}
from
'
ee/related_items_tree/constants
'
;
import
{
issuableTypesMap
}
from
'
~/related_issues/constants
'
;
import
{
mockInitialConfig
,
mockParentItem
,
mockEpics
,
mockIssues
}
from
'
../mock_data
'
;
...
...
@@ -270,5 +275,38 @@ describe('RelatedItemsTreeApp', () => {
});
},
);
it
(
'
switches tab to Roadmap
'
,
async
()
=>
{
wrapper
.
vm
.
$store
.
state
.
itemsFetchResultEmpty
=
false
;
await
nextTick
();
wrapper
.
findComponent
(
RelatedItemsTreeActions
).
vm
.
$emit
(
'
tab-change
'
,
ITEM_TABS
.
ROADMAP
);
await
nextTick
();
expect
(
wrapper
.
vm
.
activeTab
).
toBe
(
ITEM_TABS
.
ROADMAP
);
});
it
.
each
`
visibleApp | activeTab
${
'
Tree View
'
}
|
${
ITEM_TABS
.
TREE
}
${
'
Roadmap View
'
}
|
${
ITEM_TABS
.
ROADMAP
}
`
(
'
renders $visibleApp when activeTab is $activeTab
'
,
async
({
activeTab
})
=>
{
wrapper
.
vm
.
$store
.
state
.
itemsFetchResultEmpty
=
false
;
await
nextTick
();
wrapper
.
findComponent
(
RelatedItemsTreeActions
).
vm
.
$emit
(
'
tab-change
'
,
activeTab
);
await
nextTick
();
const
appMapping
=
{
[
ITEM_TABS
.
TREE
]:
RelatedItemsTreeBody
,
[
ITEM_TABS
.
ROADMAP
]:
RelatedItemsRoadmapApp
,
};
expect
(
wrapper
.
findComponent
(
appMapping
[
activeTab
]).
isVisible
()).
toBe
(
true
);
});
});
});
ee/spec/frontend/related_items_tree/components/related_items_tree_header_spec.js
View file @
e2e0cb4f
...
...
@@ -6,7 +6,6 @@ import Vuex from 'vuex';
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
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
*
as
epicUtils
from
'
ee/related_items_tree/utils/epic_utils
'
;
...
...
@@ -59,12 +58,12 @@ describe('RelatedItemsTree', () => {
it
(
'
returns string containing epic count based on available direct children within state
'
,
()
=>
{
expect
(
wrapper
.
findComponent
(
GlTooltip
).
text
()).
toContain
(
`Epics •
1 open, 1 closed`
);
1 open, 1 closed`
);
});
it
(
'
returns string containing issue count based on available direct children within state
'
,
()
=>
{
expect
(
wrapper
.
findComponent
(
GlTooltip
).
text
()).
toContain
(
`Issues •
2 open, 1 closed`
);
2 open, 1 closed`
);
});
});
...
...
@@ -78,16 +77,6 @@ describe('RelatedItemsTree', () => {
});
});
describe
(
'
toggleLabels
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
});
it
(
'
toggle labels component is visible
'
,
()
=>
{
expect
(
wrapper
.
findComponent
(
ToggleLabels
).
isVisible
()).
toBe
(
true
);
});
});
describe
(
'
epic issue actions split button
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
=
createComponent
();
...
...
ee/spec/frontend/related_items_tree/mock_data.js
View file @
e2e0cb4f
...
...
@@ -465,3 +465,19 @@ export const mockMixedFrequentlyUsedProjects = [
frequency
:
3
,
},
];
export
const
mockRoadmapAppData
=
{
epics_path
:
'
/groups/group1/-/epics.json?parent_id=1
'
,
group_id
:
'
2
'
,
iid
:
'
1
'
,
full_path
:
'
group1
'
,
empty_state_illustration
:
''
,
new_epic_path
:
'
/groups/group1/-/epics/new
'
,
list_epics_path
:
'
/groups/group1/-/epics
'
,
epics_docs_path
:
'
/help/user/group/epics/index
'
,
preset_type
:
'
MONTHS
'
,
epics_state
:
'
all
'
,
sorted_by
:
'
start_date_asc
'
,
inner_height
:
'
600
'
,
child_epics
:
'
true
'
,
};
locale/gitlab.pot
View file @
e2e0cb4f
...
...
@@ -7402,6 +7402,9 @@ msgstr ""
msgid "Child epic doesn't exist."
msgstr ""
msgid "Child issues and epics"
msgstr ""
msgid "Chinese language support using"
msgstr ""
...
...
@@ -14419,9 +14422,6 @@ msgstr ""
msgid "Epics Roadmap"
msgstr ""
msgid "Epics and Issues"
msgstr ""
msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
msgstr ""
...
...
@@ -32142,6 +32142,9 @@ msgstr ""
msgid "Roadmap settings"
msgstr ""
msgid "Roadmap view"
msgstr ""
msgid "Role"
msgstr ""
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment