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
c71dad2e
Commit
c71dad2e
authored
Dec 22, 2020
by
Florie Guibert
Committed by
Simon Knox
Dec 22, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Board refactor - Decouple boardsStore from GraphQL boards
Duplicate components to help decouple boardsStore
parent
1e1b44a0
Changes
19
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
884 additions
and
133 deletions
+884
-133
app/assets/javascripts/boards/components/board_card_layout.vue
...ssets/javascripts/boards/components/board_card_layout.vue
+2
-1
app/assets/javascripts/boards/components/issue_card_inner.vue
...assets/javascripts/boards/components/issue_card_inner.vue
+30
-20
app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
...scripts/boards/components/issue_card_inner_deprecated.vue
+245
-0
app/assets/javascripts/boards/components/issue_time_estimate.vue
...ets/javascripts/boards/components/issue_time_estimate.vue
+16
-12
app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue
...ipts/boards/components/issue_time_estimate_deprecated.vue
+48
-0
app/assets/javascripts/boards/index.js
app/assets/javascripts/boards/index.js
+9
-9
app/assets/javascripts/boards/stores/actions.js
app/assets/javascripts/boards/stores/actions.js
+7
-10
app/assets/javascripts/boards/stores/mutations.js
app/assets/javascripts/boards/stores/mutations.js
+3
-2
ee/app/assets/javascripts/boards/stores/actions.js
ee/app/assets/javascripts/boards/stores/actions.js
+4
-8
ee/spec/frontend/boards/issue_card_inner_spec.js
ee/spec/frontend/boards/issue_card_inner_spec.js
+15
-15
ee/spec/frontend/boards/stores/actions_spec.js
ee/spec/frontend/boards/stores/actions_spec.js
+11
-20
spec/frontend/boards/components/board_card_layout_spec.js
spec/frontend/boards/components/board_card_layout_spec.js
+1
-0
spec/frontend/boards/components/board_card_spec.js
spec/frontend/boards/components/board_card_spec.js
+1
-0
spec/frontend/boards/components/issue_time_estimate_deprecated_spec.js
.../boards/components/issue_time_estimate_deprecated_spec.js
+79
-0
spec/frontend/boards/components/issue_time_estimate_spec.js
spec/frontend/boards/components/issue_time_estimate_spec.js
+6
-7
spec/frontend/boards/issue_card_deprecated_spec.js
spec/frontend/boards/issue_card_deprecated_spec.js
+1
-1
spec/frontend/boards/issue_card_inner_spec.js
spec/frontend/boards/issue_card_inner_spec.js
+382
-0
spec/frontend/boards/stores/actions_spec.js
spec/frontend/boards/stores/actions_spec.js
+18
-18
spec/frontend/boards/stores/mutations_spec.js
spec/frontend/boards/stores/mutations_spec.js
+6
-10
No files found.
app/assets/javascripts/boards/components/board_card_layout.vue
View file @
c71dad2e
<
script
>
<
script
>
import
IssueCardInner
from
'
./issue_card_inner.vue
'
;
import
IssueCardInner
from
'
./issue_card_inner.vue
'
;
import
IssueCardInnerDeprecated
from
'
./issue_card_inner_deprecated.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
export
default
{
export
default
{
name
:
'
BoardsIssueCard
'
,
name
:
'
BoardsIssueCard
'
,
components
:
{
components
:
{
IssueCardInner
,
IssueCardInner
:
gon
.
features
?.
graphqlBoardLists
?
IssueCardInner
:
IssueCardInnerDeprecated
,
},
},
props
:
{
props
:
{
list
:
{
list
:
{
...
...
app/assets/javascripts/boards/components/issue_card_inner.vue
View file @
c71dad2e
<
script
>
<
script
>
import
{
sortBy
}
from
'
lodash
'
;
import
{
sortBy
}
from
'
lodash
'
;
import
{
mapState
}
from
'
vuex
'
;
import
{
map
Actions
,
map
State
}
from
'
vuex
'
;
import
{
GlLabel
,
GlTooltipDirective
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlLabel
,
GlTooltipDirective
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
issueCardInner
from
'
ee_else_ce/boards/mixins/issue_card_inner
'
;
import
issueCardInner
from
'
ee_else_ce/boards/mixins/issue_card_inner
'
;
import
{
sprintf
,
__
,
n__
}
from
'
~/locale
'
;
import
{
sprintf
,
__
,
n__
}
from
'
~/locale
'
;
...
@@ -8,9 +8,10 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
...
@@ -8,9 +8,10 @@ import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import
UserAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
UserAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
IssueDueDate
from
'
./issue_due_date.vue
'
;
import
IssueDueDate
from
'
./issue_due_date.vue
'
;
import
IssueTimeEstimate
from
'
./issue_time_estimate.vue
'
;
import
IssueTimeEstimate
from
'
./issue_time_estimate.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
eventHub
from
'
../eventhub
'
;
import
{
isScopedLabel
}
from
'
~/lib/utils/common_utils
'
;
import
{
isScopedLabel
}
from
'
~/lib/utils/common_utils
'
;
import
{
ListType
}
from
'
../constants
'
;
import
{
ListType
}
from
'
../constants
'
;
import
{
updateHistory
}
from
'
~/lib/utils/url_utility
'
;
export
default
{
export
default
{
components
:
{
components
:
{
...
@@ -42,7 +43,7 @@ export default {
...
@@ -42,7 +43,7 @@ export default {
default
:
false
,
default
:
false
,
},
},
},
},
inject
:
[
'
groupId
'
,
'
rootPath
'
],
inject
:
[
'
groupId
'
,
'
rootPath
'
,
'
scopedLabelsAvailable
'
],
data
()
{
data
()
{
return
{
return
{
limitBeforeCounter
:
2
,
limitBeforeCounter
:
2
,
...
@@ -52,6 +53,16 @@ export default {
...
@@ -52,6 +53,16 @@ export default {
},
},
computed
:
{
computed
:
{
...
mapState
([
'
isShowingLabels
'
]),
...
mapState
([
'
isShowingLabels
'
]),
cappedAssignees
()
{
// e.g. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
// Otherwise render up to the limitBeforeCounter
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
this
.
issue
.
assignees
.
slice
(
0
,
this
.
maxRender
);
}
return
this
.
issue
.
assignees
.
slice
(
0
,
this
.
limitBeforeCounter
);
},
numberOverLimit
()
{
numberOverLimit
()
{
return
this
.
issue
.
assignees
.
length
-
this
.
limitBeforeCounter
;
return
this
.
issue
.
assignees
.
length
-
this
.
limitBeforeCounter
;
},
},
...
@@ -98,19 +109,10 @@ export default {
...
@@ -98,19 +109,10 @@ export default {
},
},
},
},
methods
:
{
methods
:
{
...
mapActions
([
'
performSearch
'
]),
isIndexLessThanlimit
(
index
)
{
isIndexLessThanlimit
(
index
)
{
return
index
<
this
.
limitBeforeCounter
;
return
index
<
this
.
limitBeforeCounter
;
},
},
shouldRenderAssignee
(
index
)
{
// Eg. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
// Otherwise render up to the limitBeforeCounter
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
index
<
this
.
maxRender
;
}
return
index
<
this
.
limitBeforeCounter
;
},
assigneeUrl
(
assignee
)
{
assigneeUrl
(
assignee
)
{
if
(
!
assignee
)
return
''
;
if
(
!
assignee
)
return
''
;
return
`
${
this
.
rootPath
}${
assignee
.
username
}
`
;
return
`
${
this
.
rootPath
}${
assignee
.
username
}
`
;
...
@@ -118,6 +120,9 @@ export default {
...
@@ -118,6 +120,9 @@ export default {
avatarUrlTitle
(
assignee
)
{
avatarUrlTitle
(
assignee
)
{
return
sprintf
(
__
(
`Avatar for %{assigneeName}`
),
{
assigneeName
:
assignee
.
name
});
return
sprintf
(
__
(
`Avatar for %{assigneeName}`
),
{
assigneeName
:
assignee
.
name
});
},
},
avatarUrl
(
assignee
)
{
return
assignee
.
avatarUrl
||
assignee
.
avatar
||
gon
.
default_avatar_url
;
},
showLabel
(
label
)
{
showLabel
(
label
)
{
if
(
!
label
.
id
)
return
false
;
if
(
!
label
.
id
)
return
false
;
return
true
;
return
true
;
...
@@ -133,13 +138,19 @@ export default {
...
@@ -133,13 +138,19 @@ export default {
},
},
filterByLabel
(
label
)
{
filterByLabel
(
label
)
{
if
(
!
this
.
updateFilters
)
return
;
if
(
!
this
.
updateFilters
)
return
;
const
labelTitle
=
encodeURIComponent
(
label
.
title
)
;
const
filterPath
=
window
.
location
.
search
?
`
${
window
.
location
.
search
}
&`
:
'
?
'
;
const
filter
=
`label_name[]=
${
labelTitle
}
`
;
const
filter
=
`label_name[]=
${
encodeURIComponent
(
label
.
title
)
}
`
;
boardsStore
.
toggleFilter
(
filter
);
if
(
!
filterPath
.
includes
(
filter
))
{
updateHistory
({
url
:
`
${
filterPath
}${
filter
}
`
,
});
this
.
performSearch
();
eventHub
.
$emit
(
'
updateTokens
'
);
}
},
},
showScopedLabel
(
label
)
{
showScopedLabel
(
label
)
{
return
boardsStore
.
scopedLabels
.
enabled
&&
isScopedLabel
(
label
);
return
this
.
scopedLabelsAvailable
&&
isScopedLabel
(
label
);
},
},
},
},
};
};
...
@@ -222,12 +233,11 @@ export default {
...
@@ -222,12 +233,11 @@ export default {
</div>
</div>
<div
class=
"board-card-assignee gl-display-flex"
>
<div
class=
"board-card-assignee gl-display-flex"
>
<user-avatar-link
<user-avatar-link
v-for=
"(assignee, index) in issue.assignees"
v-for=
"assignee in cappedAssignees"
v-if=
"shouldRenderAssignee(index)"
:key=
"assignee.id"
:key=
"assignee.id"
:link-href=
"assigneeUrl(assignee)"
:link-href=
"assigneeUrl(assignee)"
:img-alt=
"avatarUrlTitle(assignee)"
:img-alt=
"avatarUrlTitle(assignee)"
:img-src=
"a
ssignee.avatarUrl || assignee.avatar || assignee.avatar_url
"
:img-src=
"a
vatarUrl(assignee)
"
:img-size=
"24"
:img-size=
"24"
class=
"js-no-trigger"
class=
"js-no-trigger"
tooltip-placement=
"bottom"
tooltip-placement=
"bottom"
...
...
app/assets/javascripts/boards/components/issue_card_inner_deprecated.vue
0 → 100644
View file @
c71dad2e
<
script
>
import
{
sortBy
}
from
'
lodash
'
;
import
{
mapState
}
from
'
vuex
'
;
import
{
GlLabel
,
GlTooltipDirective
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
issueCardInner
from
'
ee_else_ce/boards/mixins/issue_card_inner
'
;
import
{
sprintf
,
__
,
n__
}
from
'
~/locale
'
;
import
TooltipOnTruncate
from
'
~/vue_shared/components/tooltip_on_truncate.vue
'
;
import
UserAvatarLink
from
'
../../vue_shared/components/user_avatar/user_avatar_link.vue
'
;
import
IssueDueDate
from
'
./issue_due_date.vue
'
;
import
IssueTimeEstimate
from
'
./issue_time_estimate_deprecated.vue
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
import
{
isScopedLabel
}
from
'
~/lib/utils/common_utils
'
;
export
default
{
components
:
{
GlLabel
,
GlIcon
,
UserAvatarLink
,
TooltipOnTruncate
,
IssueDueDate
,
IssueTimeEstimate
,
IssueCardWeight
:
()
=>
import
(
'
ee_component/boards/components/issue_card_weight.vue
'
),
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
mixins
:
[
issueCardInner
],
props
:
{
issue
:
{
type
:
Object
,
required
:
true
,
},
list
:
{
type
:
Object
,
required
:
false
,
default
:
()
=>
({}),
},
updateFilters
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
inject
:
[
'
groupId
'
,
'
rootPath
'
],
data
()
{
return
{
limitBeforeCounter
:
2
,
maxRender
:
3
,
maxCounter
:
99
,
};
},
computed
:
{
...
mapState
([
'
isShowingLabels
'
]),
numberOverLimit
()
{
return
this
.
issue
.
assignees
.
length
-
this
.
limitBeforeCounter
;
},
assigneeCounterTooltip
()
{
const
{
numberOverLimit
,
maxCounter
}
=
this
;
const
count
=
numberOverLimit
>
maxCounter
?
maxCounter
:
numberOverLimit
;
return
sprintf
(
__
(
'
%{count} more assignees
'
),
{
count
});
},
assigneeCounterLabel
()
{
if
(
this
.
numberOverLimit
>
this
.
maxCounter
)
{
return
`
${
this
.
maxCounter
}
+`
;
}
return
`+
${
this
.
numberOverLimit
}
`
;
},
shouldRenderCounter
()
{
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
false
;
}
return
this
.
issue
.
assignees
.
length
>
this
.
numberOverLimit
;
},
issueId
()
{
if
(
this
.
issue
.
iid
)
{
return
`#
${
this
.
issue
.
iid
}
`
;
}
return
false
;
},
showLabelFooter
()
{
return
this
.
isShowingLabels
&&
this
.
issue
.
labels
.
find
(
this
.
showLabel
);
},
issueReferencePath
()
{
const
{
referencePath
,
groupId
}
=
this
.
issue
;
return
!
groupId
?
referencePath
.
split
(
'
#
'
)[
0
]
:
null
;
},
orderedLabels
()
{
return
sortBy
(
this
.
issue
.
labels
.
filter
(
this
.
isNonListLabel
),
'
title
'
);
},
blockedLabel
()
{
if
(
this
.
issue
.
blockedByCount
)
{
return
n__
(
`Blocked by %d issue`
,
`Blocked by %d issues`
,
this
.
issue
.
blockedByCount
);
}
return
__
(
'
Blocked issue
'
);
},
},
methods
:
{
isIndexLessThanlimit
(
index
)
{
return
index
<
this
.
limitBeforeCounter
;
},
shouldRenderAssignee
(
index
)
{
// Eg. maxRender is 4,
// Render up to all 4 assignees if there are only 4 assigness
// Otherwise render up to the limitBeforeCounter
if
(
this
.
issue
.
assignees
.
length
<=
this
.
maxRender
)
{
return
index
<
this
.
maxRender
;
}
return
index
<
this
.
limitBeforeCounter
;
},
assigneeUrl
(
assignee
)
{
if
(
!
assignee
)
return
''
;
return
`
${
this
.
rootPath
}${
assignee
.
username
}
`
;
},
avatarUrlTitle
(
assignee
)
{
return
sprintf
(
__
(
`Avatar for %{assigneeName}`
),
{
assigneeName
:
assignee
.
name
});
},
showLabel
(
label
)
{
if
(
!
label
.
id
)
return
false
;
return
true
;
},
isNonListLabel
(
label
)
{
return
label
.
id
&&
!
(
this
.
list
.
type
===
'
label
'
&&
this
.
list
.
title
===
label
.
title
);
},
filterByLabel
(
label
)
{
if
(
!
this
.
updateFilters
)
return
;
const
labelTitle
=
encodeURIComponent
(
label
.
title
);
const
filter
=
`label_name[]=
${
labelTitle
}
`
;
boardsStore
.
toggleFilter
(
filter
);
},
showScopedLabel
(
label
)
{
return
boardsStore
.
scopedLabels
.
enabled
&&
isScopedLabel
(
label
);
},
},
};
</
script
>
<
template
>
<div>
<div
class=
"gl-display-flex"
dir=
"auto"
>
<h4
class=
"board-card-title gl-mb-0 gl-mt-0"
>
<gl-icon
v-if=
"issue.blocked"
v-gl-tooltip
name=
"issue-block"
:title=
"blockedLabel"
class=
"issue-blocked-icon gl-mr-2"
:aria-label=
"blockedLabel"
data-testid=
"issue-blocked-icon"
/>
<gl-icon
v-if=
"issue.confidential"
v-gl-tooltip
name=
"eye-slash"
:title=
"__('Confidential')"
class=
"confidential-icon gl-mr-2"
:aria-label=
"__('Confidential')"
/>
<a
:href=
"issue.path || issue.webUrl || ''"
:title=
"issue.title"
class=
"js-no-trigger"
@
mousemove
.
stop
>
{{
issue
.
title
}}
</a
>
</h4>
</div>
<div
v-if=
"showLabelFooter"
class=
"board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap"
>
<template
v-for=
"label in orderedLabels"
>
<gl-label
:key=
"label.id"
:background-color=
"label.color"
:title=
"label.title"
:description=
"label.description"
size=
"sm"
:scoped=
"showScopedLabel(label)"
@
click=
"filterByLabel(label)"
/>
</
template
>
</div>
<div
class=
"board-card-footer gl-display-flex gl-justify-content-space-between gl-align-items-flex-end"
>
<div
class=
"gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden js-board-card-number-container"
>
<span
v-if=
"issue.referencePath"
class=
"board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
>
<tooltip-on-truncate
v-if=
"issueReferencePath"
:title=
"issueReferencePath"
placement=
"bottom"
class=
"board-issue-path gl-text-truncate gl-font-weight-bold"
>
{{ issueReferencePath }}
</tooltip-on-truncate
>
#{{ issue.iid }}
</span>
<span
class=
"board-info-items gl-mt-3 gl-display-inline-block"
>
<issue-due-date
v-if=
"issue.dueDate"
:date=
"issue.dueDate"
:closed=
"issue.closed || Boolean(issue.closedAt)"
/>
<issue-time-estimate
v-if=
"issue.timeEstimate"
:estimate=
"issue.timeEstimate"
/>
<issue-card-weight
v-if=
"validIssueWeight"
:weight=
"issue.weight"
@
click=
"filterByWeight(issue.weight)"
/>
</span>
</div>
<div
class=
"board-card-assignee gl-display-flex"
>
<user-avatar-link
v-for=
"(assignee, index) in issue.assignees"
v-if=
"shouldRenderAssignee(index)"
:key=
"assignee.id"
:link-href=
"assigneeUrl(assignee)"
:img-alt=
"avatarUrlTitle(assignee)"
:img-src=
"assignee.avatarUrl || assignee.avatar || assignee.avatar_url"
:img-size=
"24"
class=
"js-no-trigger"
tooltip-placement=
"bottom"
>
<span
class=
"js-assignee-tooltip"
>
<span
class=
"gl-font-weight-bold gl-display-block"
>
{{ __('Assignee') }}
</span>
{{ assignee.name }}
<span
class=
"text-white-50"
>
@{{ assignee.username }}
</span>
</span>
</user-avatar-link>
<span
v-if=
"shouldRenderCounter"
v-gl-tooltip
:title=
"assigneeCounterTooltip"
class=
"avatar-counter"
data-placement=
"bottom"
>
{{ assigneeCounterLabel }}
</span
>
</div>
</div>
</div>
</template>
app/assets/javascripts/boards/components/issue_time_estimate.vue
View file @
c71dad2e
<
script
>
<
script
>
import
{
GlTooltip
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
GlTooltip
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
parseSeconds
,
stringifyTime
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
parseSeconds
,
stringifyTime
}
from
'
~/lib/utils/datetime_utility
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
export
default
{
export
default
{
i18n
:
{
timeEstimate
:
__
(
'
Time estimate
'
),
},
components
:
{
components
:
{
GlIcon
,
GlIcon
,
GlTooltip
,
GlTooltip
,
...
@@ -14,17 +17,18 @@ export default {
...
@@ -14,17 +17,18 @@ export default {
required
:
true
,
required
:
true
,
},
},
},
},
data
()
{
inject
:
[
'
timeTrackingLimitToHours
'
],
return
{
limitToHours
:
boardsStore
.
timeTracking
.
limitToHours
,
};
},
computed
:
{
computed
:
{
title
()
{
title
()
{
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
limitToHours
}),
true
);
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
timeTrackingLimitToHours
}),
true
,
);
},
},
timeEstimate
()
{
timeEstimate
()
{
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
limitToHours
}));
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
timeTrackingLimitToHours
}),
);
},
},
},
},
};
};
...
@@ -33,16 +37,16 @@ export default {
...
@@ -33,16 +37,16 @@ export default {
<
template
>
<
template
>
<span>
<span>
<span
ref=
"issueTimeEstimate"
class=
"board-card-info card-number"
>
<span
ref=
"issueTimeEstimate"
class=
"board-card-info card-number"
>
<gl-icon
name=
"hourglass"
class=
"board-card-info-icon"
/><time
class=
"board-card-info-text"
>
{{
<gl-icon
name=
"hourglass"
class=
"board-card-info-icon"
/>
timeEstimate
<time
class=
"board-card-info-text"
>
{{
timeEstimate
}}
</time>
}}
</time>
</span>
</span>
<gl-tooltip
<gl-tooltip
:target=
"() => $refs.issueTimeEstimate"
:target=
"() => $refs.issueTimeEstimate"
placement=
"bottom"
placement=
"bottom"
class=
"js-issue-time-estimate"
class=
"js-issue-time-estimate"
>
>
<span
class=
"bold d-block"
>
{{
__
(
'
Time estimate
'
)
}}
</span>
{{
title
}}
<span
class=
"gl-font-weight-bold gl-display-block"
>
{{
$options
.
i18n
.
timeEstimate
}}
</span>
{{
title
}}
</gl-tooltip>
</gl-tooltip>
</span>
</span>
</
template
>
</
template
>
app/assets/javascripts/boards/components/issue_time_estimate_deprecated.vue
0 → 100644
View file @
c71dad2e
<
script
>
import
{
GlTooltip
,
GlIcon
}
from
'
@gitlab/ui
'
;
import
{
parseSeconds
,
stringifyTime
}
from
'
~/lib/utils/datetime_utility
'
;
import
boardsStore
from
'
../stores/boards_store
'
;
export
default
{
components
:
{
GlIcon
,
GlTooltip
,
},
props
:
{
estimate
:
{
type
:
Number
,
required
:
true
,
},
},
data
()
{
return
{
limitToHours
:
boardsStore
.
timeTracking
.
limitToHours
,
};
},
computed
:
{
title
()
{
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
limitToHours
}),
true
);
},
timeEstimate
()
{
return
stringifyTime
(
parseSeconds
(
this
.
estimate
,
{
limitToHours
:
this
.
limitToHours
}));
},
},
};
</
script
>
<
template
>
<span>
<span
ref=
"issueTimeEstimate"
class=
"board-card-info card-number"
>
<gl-icon
name=
"hourglass"
class=
"board-card-info-icon"
/><time
class=
"board-card-info-text"
>
{{
timeEstimate
}}
</time>
</span>
<gl-tooltip
:target=
"() => $refs.issueTimeEstimate"
placement=
"bottom"
class=
"js-issue-time-estimate"
>
<span
class=
"bold d-block"
>
{{
__
(
'
Time estimate
'
)
}}
</span>
{{
title
}}
</gl-tooltip>
</span>
</
template
>
app/assets/javascripts/boards/index.js
View file @
c71dad2e
...
@@ -117,16 +117,9 @@ export default () => {
...
@@ -117,16 +117,9 @@ export default () => {
},
},
},
},
created
()
{
created
()
{
const
endpoints
=
{
this
.
setInitialBoardData
({
boardsEndpoint
:
this
.
boardsEndpoint
,
recentBoardsEndpoint
:
this
.
recentBoardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
bulkUpdatePath
:
this
.
bulkUpdatePath
,
boardId
:
$boardApp
.
dataset
.
boardId
,
boardId
:
$boardApp
.
dataset
.
boardId
,
fullPath
:
$boardApp
.
dataset
.
fullPath
,
fullPath
:
$boardApp
.
dataset
.
fullPath
,
};
this
.
setInitialBoardData
({
...
endpoints
,
boardType
:
this
.
parent
,
boardType
:
this
.
parent
,
disabled
:
this
.
disabled
,
disabled
:
this
.
disabled
,
boardConfig
:
{
boardConfig
:
{
...
@@ -141,7 +134,14 @@ export default () => {
...
@@ -141,7 +134,14 @@ export default () => {
:
null
,
:
null
,
},
},
});
});
boardsStore
.
setEndpoints
(
endpoints
);
boardsStore
.
setEndpoints
({
boardsEndpoint
:
this
.
boardsEndpoint
,
recentBoardsEndpoint
:
this
.
recentBoardsEndpoint
,
listsEndpoint
:
this
.
listsEndpoint
,
bulkUpdatePath
:
this
.
bulkUpdatePath
,
boardId
:
$boardApp
.
dataset
.
boardId
,
fullPath
:
$boardApp
.
dataset
.
fullPath
,
});
boardsStore
.
rootPath
=
this
.
boardsEndpoint
;
boardsStore
.
rootPath
=
this
.
boardsEndpoint
;
eventHub
.
$on
(
'
updateTokens
'
,
this
.
updateTokens
);
eventHub
.
$on
(
'
updateTokens
'
,
this
.
updateTokens
);
...
...
app/assets/javascripts/boards/stores/actions.js
View file @
c71dad2e
...
@@ -78,8 +78,7 @@ export default {
...
@@ -78,8 +78,7 @@ export default {
},
},
fetchLists
:
({
commit
,
state
,
dispatch
})
=>
{
fetchLists
:
({
commit
,
state
,
dispatch
})
=>
{
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
boardType
,
filterParams
,
fullPath
,
boardId
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
...
@@ -106,7 +105,7 @@ export default {
...
@@ -106,7 +105,7 @@ export default {
},
},
createList
:
({
state
,
commit
,
dispatch
},
{
backlog
,
labelId
,
milestoneId
,
assigneeId
})
=>
{
createList
:
({
state
,
commit
,
dispatch
},
{
backlog
,
labelId
,
milestoneId
,
assigneeId
})
=>
{
const
{
boardId
}
=
state
.
endpoints
;
const
{
boardId
}
=
state
;
gqlClient
gqlClient
.
mutate
({
.
mutate
({
...
@@ -135,8 +134,7 @@ export default {
...
@@ -135,8 +134,7 @@ export default {
},
},
fetchLabels
:
({
state
,
commit
},
searchTerm
)
=>
{
fetchLabels
:
({
state
,
commit
},
searchTerm
)
=>
{
const
{
endpoints
,
boardType
}
=
state
;
const
{
fullPath
,
boardType
}
=
state
;
const
{
fullPath
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
...
@@ -227,8 +225,7 @@ export default {
...
@@ -227,8 +225,7 @@ export default {
fetchIssuesForList
:
({
state
,
commit
},
{
listId
,
fetchNext
=
false
})
=>
{
fetchIssuesForList
:
({
state
,
commit
},
{
listId
,
fetchNext
=
false
})
=>
{
commit
(
types
.
REQUEST_ISSUES_FOR_LIST
,
{
listId
,
fetchNext
});
commit
(
types
.
REQUEST_ISSUES_FOR_LIST
,
{
listId
,
fetchNext
});
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
...
@@ -271,7 +268,7 @@ export default {
...
@@ -271,7 +268,7 @@ export default {
const
originalIndex
=
fromList
.
indexOf
(
Number
(
issueId
));
const
originalIndex
=
fromList
.
indexOf
(
Number
(
issueId
));
commit
(
types
.
MOVE_ISSUE
,
{
originalIssue
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
});
commit
(
types
.
MOVE_ISSUE
,
{
originalIssue
,
fromListId
,
toListId
,
moveBeforeId
,
moveAfterId
});
const
{
boardId
}
=
state
.
endpoints
;
const
{
boardId
}
=
state
;
const
[
fullProjectPath
]
=
issuePath
.
split
(
/
[
#
]
/
);
const
[
fullProjectPath
]
=
issuePath
.
split
(
/
[
#
]
/
);
gqlClient
gqlClient
...
@@ -357,9 +354,9 @@ export default {
...
@@ -357,9 +354,9 @@ export default {
createNewIssue
:
({
commit
,
state
},
issueInput
)
=>
{
createNewIssue
:
({
commit
,
state
},
issueInput
)
=>
{
const
input
=
issueInput
;
const
input
=
issueInput
;
const
{
boardType
,
endpoints
}
=
state
;
const
{
boardType
,
fullPath
}
=
state
;
if
(
boardType
===
BoardType
.
project
)
{
if
(
boardType
===
BoardType
.
project
)
{
input
.
projectPath
=
endpoints
.
fullPath
;
input
.
projectPath
=
fullPath
;
}
}
return
gqlClient
return
gqlClient
...
...
app/assets/javascripts/boards/stores/mutations.js
View file @
c71dad2e
...
@@ -32,8 +32,9 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
...
@@ -32,8 +32,9 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter
export
default
{
export
default
{
[
mutationTypes
.
SET_INITIAL_BOARD_DATA
](
state
,
data
)
{
[
mutationTypes
.
SET_INITIAL_BOARD_DATA
](
state
,
data
)
{
const
{
boardType
,
disabled
,
boardConfig
,
...
endpoints
}
=
data
;
const
{
boardType
,
disabled
,
boardId
,
fullPath
,
boardConfig
}
=
data
;
state
.
endpoints
=
endpoints
;
state
.
boardId
=
boardId
;
state
.
fullPath
=
fullPath
;
state
.
boardType
=
boardType
;
state
.
boardType
=
boardType
;
state
.
disabled
=
disabled
;
state
.
disabled
=
disabled
;
state
.
boardConfig
=
boardConfig
;
state
.
boardConfig
=
boardConfig
;
...
...
ee/app/assets/javascripts/boards/stores/actions.js
View file @
c71dad2e
...
@@ -45,8 +45,7 @@ export const gqlClient = createGqClient(
...
@@ -45,8 +45,7 @@ export const gqlClient = createGqClient(
);
);
const
fetchAndFormatListIssues
=
(
state
,
extraVariables
)
=>
{
const
fetchAndFormatListIssues
=
(
state
,
extraVariables
)
=>
{
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
...
@@ -126,8 +125,7 @@ export default {
...
@@ -126,8 +125,7 @@ export default {
},
},
fetchEpicsSwimlanes
({
state
,
commit
,
dispatch
},
{
withLists
=
true
,
endCursor
=
null
})
{
fetchEpicsSwimlanes
({
state
,
commit
,
dispatch
},
{
withLists
=
true
,
endCursor
=
null
})
{
const
{
endpoints
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
,
boardType
,
filterParams
}
=
state
;
const
{
fullPath
,
boardId
}
=
endpoints
;
const
variables
=
{
const
variables
=
{
fullPath
,
fullPath
,
...
@@ -176,9 +174,7 @@ export default {
...
@@ -176,9 +174,7 @@ export default {
},
},
updateBoardEpicUserPreferences
({
commit
,
state
},
{
epicId
,
collapsed
})
{
updateBoardEpicUserPreferences
({
commit
,
state
},
{
epicId
,
collapsed
})
{
const
{
const
{
boardId
}
=
state
;
endpoints
:
{
boardId
},
}
=
state
;
const
variables
=
{
const
variables
=
{
boardId
:
fullBoardId
(
boardId
),
boardId
:
fullBoardId
(
boardId
),
...
@@ -394,7 +390,7 @@ export default {
...
@@ -394,7 +390,7 @@ export default {
epicId
,
epicId
,
});
});
const
{
boardId
}
=
state
.
endpoints
;
const
{
boardId
}
=
state
;
const
[
fullProjectPath
]
=
issuePath
.
split
(
/
[
#
]
/
);
const
[
fullProjectPath
]
=
issuePath
.
split
(
/
[
#
]
/
);
gqlClient
gqlClient
...
...
ee/spec/frontend/boards/issue_card_spec.js
→
ee/spec/frontend/boards/issue_card_
inner_
spec.js
View file @
c71dad2e
import
{
GlLabel
}
from
'
@gitlab/ui
'
;
import
{
GlLabel
}
from
'
@gitlab/ui
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
IssueCardWeight
from
'
ee/boards/components/issue_card_weight.vue
'
;
import
IssueCardWeight
from
'
ee/boards/components/issue_card_weight.vue
'
;
import
ListIssueEE
from
'
ee/boards/models/issue
'
;
import
IssueCardInner
from
'
~/boards/components/issue_card_inner.vue
'
;
import
IssueCardInner
from
'
~/boards/components/issue_card_inner.vue
'
;
import
ListLabel
from
'
~/boards/models/label
'
;
import
defaultStore
from
'
~/boards/stores
'
;
import
defaultStore
from
'
~/boards/stores
'
;
describe
(
'
Issue card component
'
,
()
=>
{
describe
(
'
Issue card component
'
,
()
=>
{
...
@@ -22,6 +20,7 @@ describe('Issue card component', () => {
...
@@ -22,6 +20,7 @@ describe('Issue card component', () => {
provide
:
{
provide
:
{
groupId
:
null
,
groupId
:
null
,
rootPath
:
'
/
'
,
rootPath
:
'
/
'
,
scopedLabelsAvailable
:
false
,
},
},
});
});
};
};
...
@@ -31,7 +30,7 @@ describe('Issue card component', () => {
...
@@ -31,7 +30,7 @@ describe('Issue card component', () => {
id
:
300
,
id
:
300
,
position
:
0
,
position
:
0
,
title
:
'
Test
'
,
title
:
'
Test
'
,
list
_t
ype
:
'
label
'
,
list
T
ype
:
'
label
'
,
label
:
{
label
:
{
id
:
5000
,
id
:
5000
,
title
:
'
Testing
'
,
title
:
'
Testing
'
,
...
@@ -41,19 +40,19 @@ describe('Issue card component', () => {
...
@@ -41,19 +40,19 @@ describe('Issue card component', () => {
},
},
};
};
issue
=
new
ListIssueEE
(
{
issue
=
{
title
:
'
Testing
'
,
title
:
'
Testing
'
,
id
:
1
,
id
:
1
,
iid
:
1
,
iid
:
1
,
confidential
:
false
,
confidential
:
false
,
labels
:
[
list
.
label
],
labels
:
[
list
.
label
],
assignees
:
[],
assignees
:
[],
reference
_p
ath
:
'
#1
'
,
reference
P
ath
:
'
#1
'
,
real_path
:
'
/test/1
'
,
webUrl
:
'
/test/1
'
,
weight
:
1
,
weight
:
1
,
blocked
:
true
,
blocked
:
true
,
blockedByCount
:
2
,
blockedByCount
:
2
,
}
)
;
};
});
});
afterEach
(()
=>
{
afterEach
(()
=>
{
...
@@ -63,15 +62,15 @@ describe('Issue card component', () => {
...
@@ -63,15 +62,15 @@ describe('Issue card component', () => {
describe
(
'
labels
'
,
()
=>
{
describe
(
'
labels
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
const
label1
=
new
ListLabel
(
{
const
label1
=
{
id
:
3
,
id
:
3
,
title
:
'
testing 123
'
,
title
:
'
testing 123
'
,
color
:
'
#000cff
'
,
color
:
'
#000cff
'
,
text
_c
olor
:
'
white
'
,
text
C
olor
:
'
white
'
,
description
:
'
test
'
,
description
:
'
test
'
,
}
)
;
};
issue
.
addLabel
(
label1
)
;
issue
.
labels
=
[...
issue
.
labels
,
label1
]
;
});
});
it
.
each
`
it
.
each
`
...
@@ -79,14 +78,15 @@ describe('Issue card component', () => {
...
@@ -79,14 +78,15 @@ describe('Issue card component', () => {
${
'
GroupLabel
'
}
|
${
'
Group label
'
}
|
${
'
shows group labels on group boards
'
}
${
'
GroupLabel
'
}
|
${
'
Group label
'
}
|
${
'
shows group labels on group boards
'
}
${
'
ProjectLabel
'
}
|
${
'
Project label
'
}
|
${
'
shows project labels on group boards
'
}
${
'
ProjectLabel
'
}
|
${
'
Project label
'
}
|
${
'
shows project labels on group boards
'
}
`
(
'
$desc
'
,
({
type
,
title
})
=>
{
`
(
'
$desc
'
,
({
type
,
title
})
=>
{
issue
.
addLabel
(
issue
.
labels
=
[
new
ListLabel
({
...
issue
.
labels
,
{
id
:
9001
,
id
:
9001
,
type
,
type
,
title
,
title
,
color
:
'
#000000
'
,
color
:
'
#000000
'
,
}
)
,
},
)
;
]
;
createComponent
({
groupId
:
1
});
createComponent
({
groupId
:
1
});
...
...
ee/spec/frontend/boards/stores/actions_spec.js
View file @
c71dad2e
...
@@ -137,10 +137,8 @@ describe('performSearch', () => {
...
@@ -137,10 +137,8 @@ describe('performSearch', () => {
describe
(
'
fetchEpicsSwimlanes
'
,
()
=>
{
describe
(
'
fetchEpicsSwimlanes
'
,
()
=>
{
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
1
,
},
filterParams
:
{},
filterParams
:
{},
boardType
:
'
group
'
,
boardType
:
'
group
'
,
};
};
...
@@ -230,9 +228,7 @@ describe('fetchEpicsSwimlanes', () => {
...
@@ -230,9 +228,7 @@ describe('fetchEpicsSwimlanes', () => {
describe
(
'
updateBoardEpicUserPreferences
'
,
()
=>
{
describe
(
'
updateBoardEpicUserPreferences
'
,
()
=>
{
const
state
=
{
const
state
=
{
endpoints
:
{
boardId
:
1
,
boardId
:
1
,
},
};
};
const
queryResponse
=
(
collapsed
=
false
)
=>
({
const
queryResponse
=
(
collapsed
=
false
)
=>
({
...
@@ -392,10 +388,8 @@ describe('fetchIssuesForEpic', () => {
...
@@ -392,10 +388,8 @@ describe('fetchIssuesForEpic', () => {
const
epicId
=
mockEpic
.
id
;
const
epicId
=
mockEpic
.
id
;
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
1
,
},
filterParams
:
{},
filterParams
:
{},
boardType
:
'
group
'
,
boardType
:
'
group
'
,
};
};
...
@@ -463,10 +457,8 @@ describe('toggleEpicSwimlanes', () => {
...
@@ -463,10 +457,8 @@ describe('toggleEpicSwimlanes', () => {
const
state
=
{
const
state
=
{
isShowingEpicsSwimlanes
:
false
,
isShowingEpicsSwimlanes
:
false
,
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
1
,
},
};
};
return
testAction
(
return
testAction
(
...
@@ -495,10 +487,8 @@ describe('toggleEpicSwimlanes', () => {
...
@@ -495,10 +487,8 @@ describe('toggleEpicSwimlanes', () => {
const
state
=
{
const
state
=
{
isShowingEpicsSwimlanes
:
true
,
isShowingEpicsSwimlanes
:
true
,
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
1
,
},
};
};
return
testAction
(
return
testAction
(
...
@@ -635,7 +625,8 @@ describe('moveIssue', () => {
...
@@ -635,7 +625,8 @@ describe('moveIssue', () => {
};
};
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
mockLists
,
boardLists
:
mockLists
,
...
...
spec/frontend/boards/components/board_card_layout_spec.js
View file @
c71dad2e
...
@@ -38,6 +38,7 @@ describe('Board card layout', () => {
...
@@ -38,6 +38,7 @@ describe('Board card layout', () => {
provide
:
{
provide
:
{
groupId
:
null
,
groupId
:
null
,
rootPath
:
'
/
'
,
rootPath
:
'
/
'
,
scopedLabelsAvailable
:
false
,
},
},
});
});
};
};
...
...
spec/frontend/boards/components/board_card_spec.js
View file @
c71dad2e
...
@@ -45,6 +45,7 @@ describe('BoardCard', () => {
...
@@ -45,6 +45,7 @@ describe('BoardCard', () => {
provide
:
{
provide
:
{
groupId
:
null
,
groupId
:
null
,
rootPath
:
'
/
'
,
rootPath
:
'
/
'
,
scopedLabelsAvailable
:
false
,
},
},
});
});
};
};
...
...
spec/frontend/boards/components/issue_time_estimate_deprecated_spec.js
0 → 100644
View file @
c71dad2e
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
IssueTimeEstimate
from
'
~/boards/components/issue_time_estimate_deprecated.vue
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
describe
(
'
Issue Time Estimate component
'
,
()
=>
{
let
wrapper
;
beforeEach
(()
=>
{
boardsStore
.
create
();
});
afterEach
(()
=>
{
wrapper
.
destroy
();
});
describe
(
'
when limitToHours is false
'
,
()
=>
{
beforeEach
(()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
false
;
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
propsData
:
{
estimate
:
374460
,
},
});
});
it
(
'
renders the correct time estimate
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
time
'
)
.
text
()
.
trim
(),
).
toEqual
(
'
2w 3d 1m
'
);
});
it
(
'
renders expanded time estimate in tooltip
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-issue-time-estimate
'
).
text
()).
toContain
(
'
2 weeks 3 days 1 minute
'
);
});
it
(
'
prevents tooltip xss
'
,
done
=>
{
const
alertSpy
=
jest
.
spyOn
(
window
,
'
alert
'
);
wrapper
.
setProps
({
estimate
:
'
Foo <script>alert("XSS")</script>
'
});
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
alertSpy
).
not
.
toHaveBeenCalled
();
expect
(
wrapper
.
find
(
'
time
'
)
.
text
()
.
trim
(),
).
toEqual
(
'
0m
'
);
expect
(
wrapper
.
find
(
'
.js-issue-time-estimate
'
).
text
()).
toContain
(
'
0m
'
);
done
();
});
});
});
describe
(
'
when limitToHours is true
'
,
()
=>
{
beforeEach
(()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
true
;
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
propsData
:
{
estimate
:
374460
,
},
});
});
it
(
'
renders the correct time estimate
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
time
'
)
.
text
()
.
trim
(),
).
toEqual
(
'
104h 1m
'
);
});
it
(
'
renders expanded time estimate in tooltip
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-issue-time-estimate
'
).
text
()).
toContain
(
'
104 hours 1 minute
'
);
});
});
});
spec/frontend/boards/components/issue_time_estimate_spec.js
View file @
c71dad2e
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
IssueTimeEstimate
from
'
~/boards/components/issue_time_estimate.vue
'
;
import
IssueTimeEstimate
from
'
~/boards/components/issue_time_estimate.vue
'
;
import
boardsStore
from
'
~/boards/stores/boards_store
'
;
describe
(
'
Issue Time Estimate component
'
,
()
=>
{
describe
(
'
Issue Time Estimate component
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
beforeEach
(()
=>
{
boardsStore
.
create
();
});
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
});
});
describe
(
'
when limitToHours is false
'
,
()
=>
{
describe
(
'
when limitToHours is false
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
false
;
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
propsData
:
{
propsData
:
{
estimate
:
374460
,
estimate
:
374460
,
},
},
provide
:
{
timeTrackingLimitToHours
:
false
,
},
});
});
});
});
...
@@ -55,11 +52,13 @@ describe('Issue Time Estimate component', () => {
...
@@ -55,11 +52,13 @@ describe('Issue Time Estimate component', () => {
describe
(
'
when limitToHours is true
'
,
()
=>
{
describe
(
'
when limitToHours is true
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
boardsStore
.
timeTracking
.
limitToHours
=
true
;
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
wrapper
=
shallowMount
(
IssueTimeEstimate
,
{
propsData
:
{
propsData
:
{
estimate
:
374460
,
estimate
:
374460
,
},
},
provide
:
{
timeTrackingLimitToHours
:
true
,
},
});
});
});
});
...
...
spec/frontend/boards/issue_card_spec.js
→
spec/frontend/boards/issue_card_
deprecated_
spec.js
View file @
c71dad2e
...
@@ -6,7 +6,7 @@ import '~/boards/models/assignee';
...
@@ -6,7 +6,7 @@ import '~/boards/models/assignee';
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/issue
'
;
import
'
~/boards/models/list
'
;
import
'
~/boards/models/list
'
;
import
{
GlLabel
}
from
'
@gitlab/ui
'
;
import
{
GlLabel
}
from
'
@gitlab/ui
'
;
import
IssueCardInner
from
'
~/boards/components/issue_card_inner.vue
'
;
import
IssueCardInner
from
'
~/boards/components/issue_card_inner
_deprecated
.vue
'
;
import
{
listObj
}
from
'
./mock_data
'
;
import
{
listObj
}
from
'
./mock_data
'
;
import
store
from
'
~/boards/stores
'
;
import
store
from
'
~/boards/stores
'
;
...
...
spec/frontend/boards/issue_card_inner_spec.js
0 → 100644
View file @
c71dad2e
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
range
}
from
'
lodash
'
;
import
{
GlLabel
}
from
'
@gitlab/ui
'
;
import
IssueCardInner
from
'
~/boards/components/issue_card_inner.vue
'
;
import
{
mockLabelList
}
from
'
./mock_data
'
;
import
defaultStore
from
'
~/boards/stores
'
;
import
eventHub
from
'
~/boards/eventhub
'
;
import
{
updateHistory
}
from
'
~/lib/utils/url_utility
'
;
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
jest
.
mock
(
'
~/boards/eventhub
'
);
describe
(
'
Issue card component
'
,
()
=>
{
const
user
=
{
id
:
1
,
name
:
'
testing 123
'
,
username
:
'
test
'
,
avatarUrl
:
'
test_image
'
,
};
const
label1
=
{
id
:
3
,
title
:
'
testing 123
'
,
color
:
'
#000CFF
'
,
textColor
:
'
white
'
,
description
:
'
test
'
,
};
let
wrapper
;
let
issue
;
let
list
;
const
createWrapper
=
(
props
=
{},
store
=
defaultStore
)
=>
{
wrapper
=
mount
(
IssueCardInner
,
{
store
,
propsData
:
{
list
,
issue
,
...
props
,
},
stubs
:
{
GlLabel
:
true
,
},
provide
:
{
groupId
:
null
,
rootPath
:
'
/
'
,
scopedLabelsAvailable
:
false
,
},
});
};
beforeEach
(()
=>
{
list
=
mockLabelList
;
issue
=
{
title
:
'
Testing
'
,
id
:
1
,
iid
:
1
,
confidential
:
false
,
labels
:
[
list
.
label
],
assignees
:
[],
referencePath
:
'
#1
'
,
webUrl
:
'
/test/1
'
,
weight
:
1
,
};
createWrapper
({
issue
,
list
});
});
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
jest
.
clearAllMocks
();
});
it
(
'
renders issue title
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-title
'
).
text
()).
toContain
(
issue
.
title
);
});
it
(
'
includes issue base in link
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-title a
'
).
attributes
(
'
href
'
)).
toContain
(
'
/test
'
);
});
it
(
'
includes issue title on link
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-title a
'
).
attributes
(
'
title
'
)).
toBe
(
issue
.
title
);
});
it
(
'
does not render confidential icon
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.confidential-icon
'
).
exists
()).
toBe
(
false
);
});
it
(
'
does not render blocked icon
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.issue-blocked-icon
'
).
exists
()).
toBe
(
false
);
});
it
(
'
renders issue ID with #
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-number
'
).
text
()).
toContain
(
`#
${
issue
.
id
}
`
);
});
it
(
'
does not render assignee
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee .avatar
'
).
exists
()).
toBe
(
false
);
});
describe
(
'
confidential issue
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
confidential
:
true
,
},
});
});
it
(
'
renders confidential icon
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.confidential-icon
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
with assignee
'
,
()
=>
{
describe
(
'
with avatar
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
assignees
:
[
user
],
updateData
(
newData
)
{
Object
.
assign
(
this
,
newData
);
},
},
});
});
it
(
'
renders assignee
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee .avatar
'
).
exists
()).
toBe
(
true
);
});
it
(
'
sets title
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.js-assignee-tooltip
'
).
text
()).
toContain
(
`
${
user
.
name
}
`
);
});
it
(
'
sets users path
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee a
'
).
attributes
(
'
href
'
)).
toBe
(
'
/test
'
);
});
it
(
'
renders avatar
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee img
'
).
exists
()).
toBe
(
true
);
});
it
(
'
renders the avatar using avatarUrl property
'
,
async
()
=>
{
wrapper
.
props
(
'
issue
'
).
updateData
({
...
wrapper
.
props
(
'
issue
'
),
assignees
:
[
{
id
:
'
1
'
,
name
:
'
test
'
,
state
:
'
active
'
,
username
:
'
test_name
'
,
avatarUrl
:
'
test_image_from_avatar_url
'
,
},
],
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-card-assignee img
'
).
attributes
(
'
src
'
)).
toBe
(
'
test_image_from_avatar_url?width=24
'
,
);
});
});
describe
(
'
with default avatar
'
,
()
=>
{
beforeEach
(()
=>
{
global
.
gon
.
default_avatar_url
=
'
default_avatar
'
;
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
assignees
:
[
{
id
:
1
,
name
:
'
testing 123
'
,
username
:
'
test
'
,
},
],
},
});
});
afterEach
(()
=>
{
global
.
gon
.
default_avatar_url
=
null
;
});
it
(
'
displays defaults avatar if users avatar is null
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee img
'
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
'
.board-card-assignee img
'
).
attributes
(
'
src
'
)).
toBe
(
'
default_avatar?width=24
'
,
);
});
});
});
describe
(
'
multiple assignees
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
assignees
:
[
{
id
:
2
,
name
:
'
user2
'
,
username
:
'
user2
'
,
avatarUrl
:
'
test_image
'
,
},
{
id
:
3
,
name
:
'
user3
'
,
username
:
'
user3
'
,
avatarUrl
:
'
test_image
'
,
},
{
id
:
4
,
name
:
'
user4
'
,
username
:
'
user4
'
,
avatarUrl
:
'
test_image
'
,
},
],
},
});
});
it
(
'
renders all three assignees
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.board-card-assignee .avatar
'
).
length
).
toEqual
(
3
);
});
describe
(
'
more than three assignees
'
,
()
=>
{
beforeEach
(()
=>
{
const
{
assignees
}
=
wrapper
.
props
(
'
issue
'
);
assignees
.
push
({
id
:
5
,
name
:
'
user5
'
,
username
:
'
user5
'
,
avatarUrl
:
'
test_image
'
,
});
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
assignees
,
},
});
});
it
(
'
renders more avatar counter
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.board-card-assignee .avatar-counter
'
)
.
text
()
.
trim
(),
).
toEqual
(
'
+2
'
);
});
it
(
'
renders two assignees
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
'
.board-card-assignee .avatar
'
).
length
).
toEqual
(
2
);
});
it
(
'
renders 99+ avatar counter
'
,
async
()
=>
{
const
assignees
=
[
...
wrapper
.
props
(
'
issue
'
).
assignees
,
...
range
(
5
,
103
).
map
(
i
=>
({
id
:
i
,
name
:
'
name
'
,
username
:
'
username
'
,
avatarUrl
:
'
test_image
'
,
})),
];
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
assignees
,
},
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
find
(
'
.board-card-assignee .avatar-counter
'
)
.
text
()
.
trim
(),
).
toEqual
(
'
99+
'
);
});
});
});
describe
(
'
labels
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
issue
:
{
...
issue
,
labels
:
[
list
.
label
,
label1
]
}
});
});
it
(
'
does not render list label but renders all other labels
'
,
()
=>
{
expect
(
wrapper
.
findAll
(
GlLabel
).
length
).
toBe
(
1
);
const
label
=
wrapper
.
find
(
GlLabel
);
expect
(
label
.
props
(
'
title
'
)).
toEqual
(
label1
.
title
);
expect
(
label
.
props
(
'
description
'
)).
toEqual
(
label1
.
description
);
expect
(
label
.
props
(
'
backgroundColor
'
)).
toEqual
(
label1
.
color
);
});
it
(
'
does not render label if label does not have an ID
'
,
async
()
=>
{
wrapper
.
setProps
({
issue
:
{
...
issue
,
labels
:
[
label1
,
{
title
:
'
closed
'
}]
}
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
findAll
(
GlLabel
).
length
).
toBe
(
1
);
expect
(
wrapper
.
text
()).
not
.
toContain
(
'
closed
'
);
});
});
describe
(
'
blocked
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
setProps
({
issue
:
{
...
wrapper
.
props
(
'
issue
'
),
blocked
:
true
,
},
});
});
it
(
'
renders blocked icon if issue is blocked
'
,
()
=>
{
expect
(
wrapper
.
find
(
'
.issue-blocked-icon
'
).
exists
()).
toBe
(
true
);
});
});
describe
(
'
filterByLabel method
'
,
()
=>
{
beforeEach
(()
=>
{
delete
window
.
location
;
wrapper
.
setProps
({
updateFilters
:
true
,
});
});
describe
(
'
when selected label is not in the filter
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
performSearch
'
).
mockImplementation
(()
=>
{});
window
.
location
=
{
search
:
''
};
wrapper
.
vm
.
filterByLabel
(
label1
);
});
it
(
'
calls updateHistory
'
,
()
=>
{
expect
(
updateHistory
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
dispatches performSearch vuex action
'
,
()
=>
{
expect
(
wrapper
.
vm
.
performSearch
).
toHaveBeenCalledTimes
(
1
);
});
it
(
'
emits updateTokens event
'
,
()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledTimes
(
1
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
updateTokens
'
);
});
});
describe
(
'
when selected label is already in the filter
'
,
()
=>
{
beforeEach
(()
=>
{
jest
.
spyOn
(
wrapper
.
vm
,
'
performSearch
'
).
mockImplementation
(()
=>
{});
window
.
location
=
{
search
:
'
?label_name[]=testing%20123
'
};
wrapper
.
vm
.
filterByLabel
(
label1
);
});
it
(
'
does not call updateHistory
'
,
()
=>
{
expect
(
updateHistory
).
not
.
toHaveBeenCalled
();
});
it
(
'
does not dispatch performSearch vuex action
'
,
()
=>
{
expect
(
wrapper
.
vm
.
performSearch
).
not
.
toHaveBeenCalled
();
});
it
(
'
does not emit updateTokens event
'
,
()
=>
{
expect
(
eventHub
.
$emit
).
not
.
toHaveBeenCalled
();
});
});
});
});
spec/frontend/boards/stores/actions_spec.js
View file @
c71dad2e
...
@@ -108,10 +108,8 @@ describe('setActiveId', () => {
...
@@ -108,10 +108,8 @@ describe('setActiveId', () => {
describe
(
'
fetchLists
'
,
()
=>
{
describe
(
'
fetchLists
'
,
()
=>
{
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
'
1
'
,
},
filterParams
:
{},
filterParams
:
{},
boardType
:
'
group
'
,
boardType
:
'
group
'
,
};
};
...
@@ -201,7 +199,8 @@ describe('createList', () => {
...
@@ -201,7 +199,8 @@ describe('createList', () => {
);
);
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
[{
type
:
'
closed
'
}],
boardLists
:
[{
type
:
'
closed
'
}],
...
@@ -230,7 +229,8 @@ describe('createList', () => {
...
@@ -230,7 +229,8 @@ describe('createList', () => {
);
);
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
[{
type
:
'
closed
'
}],
boardLists
:
[{
type
:
'
closed
'
}],
...
@@ -255,7 +255,8 @@ describe('moveList', () => {
...
@@ -255,7 +255,8 @@ describe('moveList', () => {
};
};
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
initialBoardListsState
,
boardLists
:
initialBoardListsState
,
...
@@ -297,7 +298,8 @@ describe('moveList', () => {
...
@@ -297,7 +298,8 @@ describe('moveList', () => {
};
};
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
initialBoardListsState
,
boardLists
:
initialBoardListsState
,
...
@@ -330,7 +332,8 @@ describe('updateList', () => {
...
@@ -330,7 +332,8 @@ describe('updateList', () => {
});
});
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
[{
type
:
'
closed
'
}],
boardLists
:
[{
type
:
'
closed
'
}],
...
@@ -429,10 +432,8 @@ describe('fetchIssuesForList', () => {
...
@@ -429,10 +432,8 @@ describe('fetchIssuesForList', () => {
const
listId
=
mockLists
[
0
].
id
;
const
listId
=
mockLists
[
0
].
id
;
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
fullPath
:
'
gitlab-org
'
,
boardId
:
1
,
boardId
:
'
1
'
,
},
filterParams
:
{},
filterParams
:
{},
boardType
:
'
group
'
,
boardType
:
'
group
'
,
};
};
...
@@ -530,7 +531,8 @@ describe('moveIssue', () => {
...
@@ -530,7 +531,8 @@ describe('moveIssue', () => {
};
};
const
state
=
{
const
state
=
{
endpoints
:
{
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
},
fullPath
:
'
gitlab-org
'
,
boardId
:
'
1
'
,
boardType
:
'
group
'
,
boardType
:
'
group
'
,
disabled
:
false
,
disabled
:
false
,
boardLists
:
mockLists
,
boardLists
:
mockLists
,
...
@@ -582,7 +584,7 @@ describe('moveIssue', () => {
...
@@ -582,7 +584,7 @@ describe('moveIssue', () => {
mutation
:
issueMoveListMutation
,
mutation
:
issueMoveListMutation
,
variables
:
{
variables
:
{
projectPath
:
getProjectPath
(
mockIssue
.
referencePath
),
projectPath
:
getProjectPath
(
mockIssue
.
referencePath
),
boardId
:
fullBoardId
(
state
.
endpoints
.
boardId
),
boardId
:
fullBoardId
(
state
.
boardId
),
iid
:
mockIssue
.
iid
,
iid
:
mockIssue
.
iid
,
fromListId
:
1
,
fromListId
:
1
,
toListId
:
2
,
toListId
:
2
,
...
@@ -724,9 +726,7 @@ describe('setAssignees', () => {
...
@@ -724,9 +726,7 @@ describe('setAssignees', () => {
describe
(
'
createNewIssue
'
,
()
=>
{
describe
(
'
createNewIssue
'
,
()
=>
{
const
state
=
{
const
state
=
{
boardType
:
'
group
'
,
boardType
:
'
group
'
,
endpoints
:
{
fullPath
:
'
gitlab-org/gitlab
'
,
fullPath
:
'
gitlab-org/gitlab
'
,
},
};
};
it
(
'
should return issue from API on success
'
,
async
()
=>
{
it
(
'
should return issue from API on success
'
,
async
()
=>
{
...
...
spec/frontend/boards/stores/mutations_spec.js
View file @
c71dad2e
...
@@ -23,14 +23,8 @@ describe('Board Store Mutations', () => {
...
@@ -23,14 +23,8 @@ describe('Board Store Mutations', () => {
describe
(
'
SET_INITIAL_BOARD_DATA
'
,
()
=>
{
describe
(
'
SET_INITIAL_BOARD_DATA
'
,
()
=>
{
it
(
'
Should set initial Boards data to state
'
,
()
=>
{
it
(
'
Should set initial Boards data to state
'
,
()
=>
{
const
endpoints
=
{
const
boardId
=
1
;
boardsEndpoint
:
'
/boards/
'
,
const
fullPath
=
'
gitlab-org
'
;
recentBoardsEndpoint
:
'
/boards/
'
,
listsEndpoint
:
'
/boards/lists
'
,
bulkUpdatePath
:
'
/boards/bulkUpdate
'
,
boardId
:
1
,
fullPath
:
'
gitlab-org
'
,
};
const
boardType
=
'
group
'
;
const
boardType
=
'
group
'
;
const
disabled
=
false
;
const
disabled
=
false
;
const
boardConfig
=
{
const
boardConfig
=
{
...
@@ -38,13 +32,15 @@ describe('Board Store Mutations', () => {
...
@@ -38,13 +32,15 @@ describe('Board Store Mutations', () => {
};
};
mutations
[
types
.
SET_INITIAL_BOARD_DATA
](
state
,
{
mutations
[
types
.
SET_INITIAL_BOARD_DATA
](
state
,
{
...
endpoints
,
boardId
,
fullPath
,
boardType
,
boardType
,
disabled
,
disabled
,
boardConfig
,
boardConfig
,
});
});
expect
(
state
.
endpoints
).
toEqual
(
endpoints
);
expect
(
state
.
boardId
).
toEqual
(
boardId
);
expect
(
state
.
fullPath
).
toEqual
(
fullPath
);
expect
(
state
.
boardType
).
toEqual
(
boardType
);
expect
(
state
.
boardType
).
toEqual
(
boardType
);
expect
(
state
.
disabled
).
toEqual
(
disabled
);
expect
(
state
.
disabled
).
toEqual
(
disabled
);
expect
(
state
.
boardConfig
).
toEqual
(
boardConfig
);
expect
(
state
.
boardConfig
).
toEqual
(
boardConfig
);
...
...
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