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
0
Merge Requests
0
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
Jérome Perrin
gitlab-ce
Commits
49b85262
Commit
49b85262
authored
Apr 19, 2018
by
Dennis Tang
Committed by
Rémy Coutable
Apr 19, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Resolve "Improve tooltips of collapsed sidebars"
parent
79905b5a
Changes
36
Show whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
458 additions
and
170 deletions
+458
-170
app/assets/javascripts/due_date_select.js
app/assets/javascripts/due_date_select.js
+8
-1
app/assets/javascripts/labels_select.js
app/assets/javascripts/labels_select.js
+2
-3
app/assets/javascripts/milestone_select.js
app/assets/javascripts/milestone_select.js
+10
-4
app/assets/javascripts/right_sidebar.js
app/assets/javascripts/right_sidebar.js
+9
-3
app/assets/javascripts/sidebar/components/assignees/assignees.vue
...ts/javascripts/sidebar/components/assignees/assignees.vue
+19
-1
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
...cripts/sidebar/components/assignees/sidebar_assignees.vue
+9
-3
app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
...ar/components/confidential/confidential_issue_sidebar.vue
+15
-4
app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
...avascripts/sidebar/components/lock/lock_issue_sidebar.vue
+18
-3
app/assets/javascripts/sidebar/components/participants/participants.vue
...ascripts/sidebar/components/participants/participants.vue
+14
-4
app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
...ipts/sidebar/components/time_tracking/collapsed_state.vue
+29
-3
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+1
-0
app/assets/javascripts/users_select.js
app/assets/javascripts/users_select.js
+5
-2
app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
...ascripts/vue_shared/components/sidebar/toggle_sidebar.vue
+27
-12
app/assets/stylesheets/framework/variables.scss
app/assets/stylesheets/framework/variables.scss
+3
-0
app/assets/stylesheets/pages/issuable.scss
app/assets/stylesheets/pages/issuable.scss
+25
-3
app/assets/stylesheets/pages/milestone.scss
app/assets/stylesheets/pages/milestone.scss
+10
-4
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+34
-4
app/helpers/milestones_helper.rb
app/helpers/milestones_helper.rb
+74
-17
app/models/concerns/milestoneish.rb
app/models/concerns/milestoneish.rb
+2
-2
app/serializers/entity_date_helper.rb
app/serializers/entity_date_helper.rb
+27
-0
app/views/projects/issues/_issue.html.haml
app/views/projects/issues/_issue.html.haml
+1
-1
app/views/projects/merge_requests/_merge_request.html.haml
app/views/projects/merge_requests/_merge_request.html.haml
+1
-1
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+5
-6
app/views/shared/issuable/_sidebar_assignees.html.haml
app/views/shared/issuable/_sidebar_assignees.html.haml
+1
-1
app/views/shared/issuable/_sidebar_todo.html.haml
app/views/shared/issuable/_sidebar_todo.html.haml
+3
-3
app/views/shared/issuable/form/_merge_request_assignee.html.haml
...ws/shared/issuable/form/_merge_request_assignee.html.haml
+1
-1
app/views/shared/milestones/_sidebar.html.haml
app/views/shared/milestones/_sidebar.html.haml
+21
-14
changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml
changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml
+5
-0
doc/user/project/issues/issues_functionalities.md
doc/user/project/issues/issues_functionalities.md
+1
-1
doc/workflow/todos.md
doc/workflow/todos.md
+2
-2
spec/features/issues/todo_spec.rb
spec/features/issues/todo_spec.rb
+2
-2
spec/helpers/issuables_helper_spec.rb
spec/helpers/issuables_helper_spec.rb
+6
-2
spec/helpers/milestones_helper_spec.rb
spec/helpers/milestones_helper_spec.rb
+0
-54
spec/javascripts/collapsed_sidebar_todo_spec.js
spec/javascripts/collapsed_sidebar_todo_spec.js
+5
-5
spec/models/milestone_spec.rb
spec/models/milestone_spec.rb
+8
-4
spec/serializers/entity_date_helper_spec.rb
spec/serializers/entity_date_helper_spec.rb
+55
-0
No files found.
app/assets/javascripts/due_date_select.js
View file @
49b85262
...
@@ -2,7 +2,9 @@
...
@@ -2,7 +2,9 @@
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
Pikaday
from
'
pikaday
'
;
import
Pikaday
from
'
pikaday
'
;
import
{
__
}
from
'
~/locale
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
{
timeFor
}
from
'
./lib/utils/datetime_utility
'
;
import
{
parsePikadayDate
,
pikadayToString
}
from
'
./lib/utils/datefix
'
;
import
{
parsePikadayDate
,
pikadayToString
}
from
'
./lib/utils/datefix
'
;
class
DueDateSelect
{
class
DueDateSelect
{
...
@@ -14,6 +16,7 @@ class DueDateSelect {
...
@@ -14,6 +16,7 @@ class DueDateSelect {
this
.
$dropdownParent
=
$dropdownParent
;
this
.
$dropdownParent
=
$dropdownParent
;
this
.
$datePicker
=
$dropdownParent
.
find
(
'
.js-due-date-calendar
'
);
this
.
$datePicker
=
$dropdownParent
.
find
(
'
.js-due-date-calendar
'
);
this
.
$block
=
$block
;
this
.
$block
=
$block
;
this
.
$sidebarCollapsedValue
=
$block
.
find
(
'
.sidebar-collapsed-icon
'
);
this
.
$selectbox
=
$dropdown
.
closest
(
'
.selectbox
'
);
this
.
$selectbox
=
$dropdown
.
closest
(
'
.selectbox
'
);
this
.
$value
=
$block
.
find
(
'
.value
'
);
this
.
$value
=
$block
.
find
(
'
.value
'
);
this
.
$valueContent
=
$block
.
find
(
'
.value-content
'
);
this
.
$valueContent
=
$block
.
find
(
'
.value-content
'
);
...
@@ -128,7 +131,8 @@ class DueDateSelect {
...
@@ -128,7 +131,8 @@ class DueDateSelect {
submitSelectedDate
(
isDropdown
)
{
submitSelectedDate
(
isDropdown
)
{
const
selectedDateValue
=
this
.
datePayload
[
this
.
abilityName
].
due_date
;
const
selectedDateValue
=
this
.
datePayload
[
this
.
abilityName
].
due_date
;
const
displayedDateStyle
=
this
.
displayedDate
!==
'
No due date
'
?
'
bold
'
:
'
no-value
'
;
const
hasDueDate
=
this
.
displayedDate
!==
'
No due date
'
;
const
displayedDateStyle
=
hasDueDate
?
'
bold
'
:
'
no-value
'
;
this
.
$loading
.
removeClass
(
'
hidden
'
).
fadeIn
();
this
.
$loading
.
removeClass
(
'
hidden
'
).
fadeIn
();
...
@@ -145,10 +149,13 @@ class DueDateSelect {
...
@@ -145,10 +149,13 @@ class DueDateSelect {
return
axios
.
put
(
this
.
issueUpdateURL
,
this
.
datePayload
)
return
axios
.
put
(
this
.
issueUpdateURL
,
this
.
datePayload
)
.
then
(()
=>
{
.
then
(()
=>
{
const
tooltipText
=
hasDueDate
?
`
${
__
(
'
Due date
'
)}
<br />
${
selectedDateValue
}
(
${
timeFor
(
selectedDateValue
)}
)`
:
__
(
'
Due date
'
);
if
(
isDropdown
)
{
if
(
isDropdown
)
{
this
.
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
this
.
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
this
.
$dropdown
.
dropdown
(
'
toggle
'
);
this
.
$dropdown
.
dropdown
(
'
toggle
'
);
}
}
this
.
$sidebarCollapsedValue
.
attr
(
'
data-original-title
'
,
tooltipText
);
return
this
.
$loading
.
fadeOut
();
return
this
.
$loading
.
fadeOut
();
});
});
}
}
...
...
app/assets/javascripts/labels_select.js
View file @
49b85262
...
@@ -83,7 +83,7 @@ export default class LabelsSelect {
...
@@ -83,7 +83,7 @@ export default class LabelsSelect {
$dropdown
.
trigger
(
'
loading.gl.dropdown
'
);
$dropdown
.
trigger
(
'
loading.gl.dropdown
'
);
axios
.
put
(
issueUpdateURL
,
data
)
axios
.
put
(
issueUpdateURL
,
data
)
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
var
labelCount
,
template
,
labelTooltipTitle
,
labelTitles
;
var
labelCount
,
template
,
labelTooltipTitle
,
labelTitles
,
formattedLabels
;
$loading
.
fadeOut
();
$loading
.
fadeOut
();
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
$selectbox
.
hide
();
$selectbox
.
hide
();
...
@@ -115,8 +115,7 @@ export default class LabelsSelect {
...
@@ -115,8 +115,7 @@ export default class LabelsSelect {
labelTooltipTitle
=
labelTitles
.
join
(
'
,
'
);
labelTooltipTitle
=
labelTitles
.
join
(
'
,
'
);
}
}
else
{
else
{
labelTooltipTitle
=
''
;
labelTooltipTitle
=
__
(
'
Labels
'
);
$sidebarLabelTooltip
.
tooltip
(
'
destroy
'
);
}
}
$sidebarLabelTooltip
$sidebarLabelTooltip
...
...
app/assets/javascripts/milestone_select.js
View file @
49b85262
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
{
__
}
from
'
~/locale
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
{
timeFor
}
from
'
./lib/utils/datetime_utility
'
;
import
{
timeFor
}
from
'
./lib/utils/datetime_utility
'
;
import
ModalStore
from
'
./boards/stores/modal_store
'
;
import
ModalStore
from
'
./boards/stores/modal_store
'
;
...
@@ -25,7 +26,7 @@ export default class MilestoneSelect {
...
@@ -25,7 +26,7 @@ export default class MilestoneSelect {
}
}
$els
.
each
((
i
,
dropdown
)
=>
{
$els
.
each
((
i
,
dropdown
)
=>
{
let
collapsedSidebarLabelTemplate
,
milestoneLinkNoneTemplate
,
milestoneLinkTemplate
,
selectedMilestone
,
selectedMilestoneDefault
;
let
milestoneLinkNoneTemplate
,
milestoneLinkTemplate
,
selectedMilestone
,
selectedMilestoneDefault
;
const
$dropdown
=
$
(
dropdown
);
const
$dropdown
=
$
(
dropdown
);
const
projectId
=
$dropdown
.
data
(
'
projectId
'
);
const
projectId
=
$dropdown
.
data
(
'
projectId
'
);
const
milestonesUrl
=
$dropdown
.
data
(
'
milestones
'
);
const
milestonesUrl
=
$dropdown
.
data
(
'
milestones
'
);
...
@@ -52,7 +53,6 @@ export default class MilestoneSelect {
...
@@ -52,7 +53,6 @@ export default class MilestoneSelect {
if
(
issueUpdateURL
)
{
if
(
issueUpdateURL
)
{
milestoneLinkTemplate
=
_
.
template
(
'
<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>
'
);
milestoneLinkTemplate
=
_
.
template
(
'
<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>
'
);
milestoneLinkNoneTemplate
=
'
<span class="no-value">None</span>
'
;
milestoneLinkNoneTemplate
=
'
<span class="no-value">None</span>
'
;
collapsedSidebarLabelTemplate
=
_
.
template
(
'
<span class="has-tooltip" data-container="body" title="<%- name %><br /><%- remaining %>" data-placement="left" data-html="true"> <%- title %> </span>
'
);
}
}
return
$dropdown
.
glDropdown
({
return
$dropdown
.
glDropdown
({
showMenuAbove
:
showMenuAbove
,
showMenuAbove
:
showMenuAbove
,
...
@@ -214,10 +214,16 @@ export default class MilestoneSelect {
...
@@ -214,10 +214,16 @@ export default class MilestoneSelect {
data
.
milestone
.
remaining
=
timeFor
(
data
.
milestone
.
due_date
);
data
.
milestone
.
remaining
=
timeFor
(
data
.
milestone
.
due_date
);
data
.
milestone
.
name
=
data
.
milestone
.
title
;
data
.
milestone
.
name
=
data
.
milestone
.
title
;
$value
.
html
(
milestoneLinkTemplate
(
data
.
milestone
));
$value
.
html
(
milestoneLinkTemplate
(
data
.
milestone
));
return
$sidebarCollapsedValue
.
find
(
'
span
'
).
html
(
collapsedSidebarLabelTemplate
(
data
.
milestone
));
return
$sidebarCollapsedValue
.
attr
(
'
data-original-title
'
,
`
${
data
.
milestone
.
name
}
<br />
${
data
.
milestone
.
remaining
}
`
)
.
find
(
'
span
'
)
.
text
(
data
.
milestone
.
title
);
}
else
{
}
else
{
$value
.
html
(
milestoneLinkNoneTemplate
);
$value
.
html
(
milestoneLinkNoneTemplate
);
return
$sidebarCollapsedValue
.
find
(
'
span
'
).
text
(
'
No
'
);
return
$sidebarCollapsedValue
.
attr
(
'
data-original-title
'
,
__
(
'
Milestone
'
))
.
find
(
'
span
'
)
.
text
(
__
(
'
None
'
));
}
}
})
})
.
catch
(()
=>
{
.
catch
(()
=>
{
...
...
app/assets/javascripts/right_sidebar.js
View file @
49b85262
...
@@ -5,6 +5,7 @@ import _ from 'underscore';
...
@@ -5,6 +5,7 @@ import _ from 'underscore';
import
Cookies
from
'
js-cookie
'
;
import
Cookies
from
'
js-cookie
'
;
import
flash
from
'
./flash
'
;
import
flash
from
'
./flash
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
{
__
}
from
'
./locale
'
;
function
Sidebar
(
currentUser
)
{
function
Sidebar
(
currentUser
)
{
this
.
toggleTodo
=
this
.
toggleTodo
.
bind
(
this
);
this
.
toggleTodo
=
this
.
toggleTodo
.
bind
(
this
);
...
@@ -41,12 +42,14 @@ Sidebar.prototype.addEventListeners = function() {
...
@@ -41,12 +42,14 @@ Sidebar.prototype.addEventListeners = function() {
};
};
Sidebar
.
prototype
.
sidebarToggleClicked
=
function
(
e
,
triggered
)
{
Sidebar
.
prototype
.
sidebarToggleClicked
=
function
(
e
,
triggered
)
{
var
$allGutterToggleIcons
,
$this
,
$thisIcon
;
var
$allGutterToggleIcons
,
$this
,
isExpanded
,
tooltipLabel
;
e
.
preventDefault
();
e
.
preventDefault
();
$this
=
$
(
this
);
$this
=
$
(
this
);
$thisIcon
=
$this
.
find
(
'
i
'
);
isExpanded
=
$this
.
find
(
'
i
'
).
hasClass
(
'
fa-angle-double-right
'
);
tooltipLabel
=
isExpanded
?
__
(
'
Expand sidebar
'
)
:
__
(
'
Collapse sidebar
'
);
$allGutterToggleIcons
=
$
(
'
.js-sidebar-toggle i
'
);
$allGutterToggleIcons
=
$
(
'
.js-sidebar-toggle i
'
);
if
(
$thisIcon
.
hasClass
(
'
fa-angle-double-right
'
))
{
if
(
isExpanded
)
{
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-right
'
).
addClass
(
'
fa-angle-double-left
'
);
$allGutterToggleIcons
.
removeClass
(
'
fa-angle-double-right
'
).
addClass
(
'
fa-angle-double-left
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
$
(
'
aside.right-sidebar
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
$
(
'
.layout-page
'
).
removeClass
(
'
right-sidebar-expanded
'
).
addClass
(
'
right-sidebar-collapsed
'
);
...
@@ -57,6 +60,9 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
...
@@ -57,6 +60,9 @@ Sidebar.prototype.sidebarToggleClicked = function (e, triggered) {
if
(
gl
.
lazyLoader
)
gl
.
lazyLoader
.
loadCheck
();
if
(
gl
.
lazyLoader
)
gl
.
lazyLoader
.
loadCheck
();
}
}
$this
.
attr
(
'
data-original-title
'
,
tooltipLabel
);
if
(
!
triggered
)
{
if
(
!
triggered
)
{
Cookies
.
set
(
"
collapsed_gutter
"
,
$
(
'
.right-sidebar
'
).
hasClass
(
'
right-sidebar-collapsed
'
));
Cookies
.
set
(
"
collapsed_gutter
"
,
$
(
'
.right-sidebar
'
).
hasClass
(
'
right-sidebar-collapsed
'
));
}
}
...
...
app/assets/javascripts/sidebar/components/assignees/assignees.vue
View file @
49b85262
<
script
>
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
export
default
{
name
:
'
Assignees
'
,
name
:
'
Assignees
'
,
directives
:
{
tooltip
,
},
props
:
{
props
:
{
rootPath
:
{
rootPath
:
{
type
:
String
,
type
:
String
,
...
@@ -14,6 +20,11 @@ export default {
...
@@ -14,6 +20,11 @@ export default {
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
issuableType
:
{
type
:
String
,
require
:
true
,
default
:
'
issue
'
,
},
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -62,6 +73,12 @@ export default {
...
@@ -62,6 +73,12 @@ export default {
names
.
push
(
`+
${
this
.
users
.
length
-
maxRender
}
more`
);
names
.
push
(
`+
${
this
.
users
.
length
-
maxRender
}
more`
);
}
}
if
(
!
this
.
users
.
length
)
{
const
emptyTooltipLabel
=
this
.
issuableType
===
'
issue
'
?
__
(
'
Assignee(s)
'
)
:
__
(
'
Assignee
'
);
names
.
push
(
emptyTooltipLabel
);
}
return
names
.
join
(
'
,
'
);
return
names
.
join
(
'
,
'
);
},
},
sidebarAvatarCounter
()
{
sidebarAvatarCounter
()
{
...
@@ -109,7 +126,8 @@ export default {
...
@@ -109,7 +126,8 @@ export default {
<div>
<div>
<div
<div
class=
"sidebar-collapsed-icon sidebar-collapsed-user"
class=
"sidebar-collapsed-icon sidebar-collapsed-user"
:class=
"
{ 'multiple-users': hasMoreThanOneAssignee, 'has-tooltip': hasAssignees }"
:class=
"
{ 'multiple-users': hasMoreThanOneAssignee }"
v-tooltip
data-container="body"
data-container="body"
data-placement="left"
data-placement="left"
:title="collapsedTooltipTitle"
:title="collapsedTooltipTitle"
...
...
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
View file @
49b85262
<
script
>
<
script
>
import
Flash
from
'
../../../flash
'
;
import
Flash
from
'
~/flash
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
Store
from
'
~/sidebar/stores/sidebar_store
'
;
import
AssigneeTitle
from
'
./assignee_title.vue
'
;
import
AssigneeTitle
from
'
./assignee_title.vue
'
;
import
Assignees
from
'
./assignees.vue
'
;
import
Assignees
from
'
./assignees.vue
'
;
import
Store
from
'
../../stores/sidebar_store
'
;
import
eventHub
from
'
../../event_hub
'
;
export
default
{
export
default
{
name
:
'
SidebarAssignees
'
,
name
:
'
SidebarAssignees
'
,
...
@@ -25,6 +25,11 @@ export default {
...
@@ -25,6 +25,11 @@ export default {
required
:
false
,
required
:
false
,
default
:
false
,
default
:
false
,
},
},
issuableType
:
{
type
:
String
,
require
:
true
,
default
:
'
issue
'
,
},
},
},
data
()
{
data
()
{
return
{
return
{
...
@@ -90,6 +95,7 @@ export default {
...
@@ -90,6 +95,7 @@ export default {
:users=
"store.assignees"
:users=
"store.assignees"
:editable=
"store.editable"
:editable=
"store.editable"
@
assign-self=
"assignSelf"
@
assign-self=
"assignSelf"
:issuable-type=
"issuableType"
/>
/>
</div>
</div>
</
template
>
</
template
>
app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
View file @
49b85262
<
script
>
<
script
>
import
Flash
from
'
../../../flash
'
;
import
{
__
}
from
'
~/locale
'
;
import
Flash
from
'
~/flash
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
editForm
from
'
./edit_form.vue
'
;
import
editForm
from
'
./edit_form.vue
'
;
import
Icon
from
'
../../../vue_shared/components/icon.vue
'
;
import
{
__
}
from
'
../../../locale
'
;
import
eventHub
from
'
../../event_hub
'
;
export
default
{
export
default
{
components
:
{
components
:
{
editForm
,
editForm
,
Icon
,
Icon
,
},
},
directives
:
{
tooltip
,
},
props
:
{
props
:
{
isConfidential
:
{
isConfidential
:
{
required
:
true
,
required
:
true
,
...
@@ -33,6 +37,9 @@ export default {
...
@@ -33,6 +37,9 @@ export default {
confidentialityIcon
()
{
confidentialityIcon
()
{
return
this
.
isConfidential
?
'
eye-slash
'
:
'
eye
'
;
return
this
.
isConfidential
?
'
eye-slash
'
:
'
eye
'
;
},
},
tooltipLabel
()
{
return
this
.
isConfidential
?
__
(
'
Confidential
'
)
:
__
(
'
Not confidential
'
);
},
},
},
created
()
{
created
()
{
eventHub
.
$on
(
'
closeConfidentialityForm
'
,
this
.
toggleForm
);
eventHub
.
$on
(
'
closeConfidentialityForm
'
,
this
.
toggleForm
);
...
@@ -65,6 +72,10 @@ export default {
...
@@ -65,6 +72,10 @@ export default {
<div
<div
class=
"sidebar-collapsed-icon"
class=
"sidebar-collapsed-icon"
@
click=
"toggleForm"
@
click=
"toggleForm"
v-tooltip
data-container=
"body"
data-placement=
"left"
:title=
"tooltipLabel"
>
>
<icon
<icon
:name=
"confidentialityIcon"
:name=
"confidentialityIcon"
...
...
app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
View file @
49b85262
<
script
>
<
script
>
import
{
__
}
from
'
~/locale
'
;
import
Flash
from
'
~/flash
'
;
import
Flash
from
'
~/flash
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
issuableMixin
from
'
~/vue_shared/mixins/issuable
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
editForm
from
'
./edit_form.vue
'
;
import
editForm
from
'
./edit_form.vue
'
;
import
issuableMixin
from
'
../../../vue_shared/mixins/issuable
'
;
import
Icon
from
'
../../../vue_shared/components/icon.vue
'
;
import
eventHub
from
'
../../event_hub
'
;
export
default
{
export
default
{
components
:
{
components
:
{
editForm
,
editForm
,
Icon
,
Icon
,
},
},
directives
:
{
tooltip
,
},
mixins
:
[
issuableMixin
],
mixins
:
[
issuableMixin
],
props
:
{
props
:
{
...
@@ -44,6 +51,10 @@ export default {
...
@@ -44,6 +51,10 @@ export default {
isLockDialogOpen
()
{
isLockDialogOpen
()
{
return
this
.
mediator
.
store
.
isLockDialogOpen
;
return
this
.
mediator
.
store
.
isLockDialogOpen
;
},
},
tooltipLabel
()
{
return
this
.
isLocked
?
__
(
'
Locked
'
)
:
__
(
'
Unlocked
'
);
},
},
},
created
()
{
created
()
{
...
@@ -85,6 +96,10 @@ export default {
...
@@ -85,6 +96,10 @@ export default {
<div
<div
class=
"sidebar-collapsed-icon"
class=
"sidebar-collapsed-icon"
@
click=
"toggleForm"
@
click=
"toggleForm"
v-tooltip
data-container=
"body"
data-placement=
"left"
:title=
"tooltipLabel"
>
>
<icon
<icon
:name=
"lockIcon"
:name=
"lockIcon"
...
...
app/assets/javascripts/sidebar/components/participants/participants.vue
View file @
49b85262
<
script
>
<
script
>
import
{
__
,
n__
,
sprintf
}
from
'
../../../locale
'
;
import
{
__
,
n__
,
sprintf
}
from
'
~/locale
'
;
import
loadingIcon
from
'
../../../vue_shared/components/loading_icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
import
userAvatarImage
from
'
../../../vue_shared/components/user_avatar/user_avatar_image.vue
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
import
userAvatarImage
from
'
~/vue_shared/components/user_avatar/user_avatar_image.vue
'
;
export
default
{
export
default
{
directives
:
{
tooltip
,
},
components
:
{
components
:
{
loadingIcon
,
loadingIcon
,
userAvatarImage
,
userAvatarImage
,
...
@@ -72,7 +76,13 @@
...
@@ -72,7 +76,13 @@
<
template
>
<
template
>
<div>
<div>
<div
class=
"sidebar-collapsed-icon"
>
<div
class=
"sidebar-collapsed-icon"
v-tooltip
data-container=
"body"
data-placement=
"left"
:title=
"participantLabel"
>
<i
<i
class=
"fa fa-users"
class=
"fa fa-users"
aria-hidden=
"true"
aria-hidden=
"true"
...
...
app/assets/javascripts/sidebar/components/time_tracking/collapsed_state.vue
View file @
49b85262
<
script
>
<
script
>
import
icon
from
'
../../../vue_shared/components/icon.vue
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
abbreviateTime
}
from
'
../../../lib/utils/pretty_time
'
;
import
{
abbreviateTime
}
from
'
~/lib/utils/pretty_time
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
export
default
{
name
:
'
TimeTrackingCollapsedState
'
,
name
:
'
TimeTrackingCollapsedState
'
,
components
:
{
components
:
{
icon
,
icon
,
},
},
directives
:
{
tooltip
,
},
props
:
{
props
:
{
showComparisonState
:
{
showComparisonState
:
{
type
:
Boolean
,
type
:
Boolean
,
...
@@ -79,6 +84,21 @@
...
@@ -79,6 +84,21 @@
return
''
;
return
''
;
},
},
timeTrackedTooltipText
()
{
let
title
;
if
(
this
.
showComparisonState
)
{
title
=
__
(
'
Time remaining
'
);
}
else
if
(
this
.
showEstimateOnlyState
)
{
title
=
__
(
'
Estimated
'
);
}
else
if
(
this
.
showSpentOnlyState
)
{
title
=
__
(
'
Time spent
'
);
}
return
sprintf
(
'
%{title}: %{text}
'
,
({
title
,
text
:
this
.
text
}));
},
tooltipText
()
{
return
this
.
showNoTimeTrackingState
?
__
(
'
Time tracking
'
)
:
this
.
timeTrackedTooltipText
;
},
},
},
methods
:
{
methods
:
{
abbreviateTime
(
timeStr
)
{
abbreviateTime
(
timeStr
)
{
...
@@ -89,7 +109,13 @@
...
@@ -89,7 +109,13 @@
</
script
>
</
script
>
<
template
>
<
template
>
<div
class=
"sidebar-collapsed-icon"
>
<div
class=
"sidebar-collapsed-icon"
v-tooltip
data-container=
"body"
data-placement=
"left"
:title=
"tooltipText"
>
<icon
name=
"timer"
/>
<icon
name=
"timer"
/>
<div
class=
"time-tracking-collapsed-summary"
>
<div
class=
"time-tracking-collapsed-summary"
>
<div
:class=
"divClass"
>
<div
:class=
"divClass"
>
...
...
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
49b85262
...
@@ -27,6 +27,7 @@ function mountAssigneesComponent(mediator) {
...
@@ -27,6 +27,7 @@ function mountAssigneesComponent(mediator) {
mediator
,
mediator
,
field
:
el
.
dataset
.
field
,
field
:
el
.
dataset
.
field
,
signedIn
:
el
.
hasAttribute
(
'
data-signed-in
'
),
signedIn
:
el
.
hasAttribute
(
'
data-signed-in
'
),
issuableType
:
gl
.
utils
.
isInIssuePage
()
?
'
issue
'
:
'
merge_request
'
,
},
},
}),
}),
});
});
...
...
app/assets/javascripts/users_select.js
View file @
49b85262
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
import
$
from
'
jquery
'
;
import
$
from
'
jquery
'
;
import
_
from
'
underscore
'
;
import
_
from
'
underscore
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
axios
from
'
./lib/utils/axios_utils
'
;
import
{
__
}
from
'
./locale
'
;
import
ModalStore
from
'
./boards/stores/modal_store
'
;
import
ModalStore
from
'
./boards/stores/modal_store
'
;
// TODO: remove eventHub hack after code splitting refactor
// TODO: remove eventHub hack after code splitting refactor
...
@@ -182,7 +183,7 @@ function UsersSelect(currentUser, els, options = {}) {
...
@@ -182,7 +183,7 @@ function UsersSelect(currentUser, els, options = {}) {
return
axios
.
put
(
issueURL
,
data
)
return
axios
.
put
(
issueURL
,
data
)
.
then
(({
data
})
=>
{
.
then
(({
data
})
=>
{
var
user
;
var
user
,
tooltipTitle
;
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
$dropdown
.
trigger
(
'
loaded.gl.dropdown
'
);
$loading
.
fadeOut
();
$loading
.
fadeOut
();
if
(
data
.
assignee
)
{
if
(
data
.
assignee
)
{
...
@@ -191,15 +192,17 @@ function UsersSelect(currentUser, els, options = {}) {
...
@@ -191,15 +192,17 @@ function UsersSelect(currentUser, els, options = {}) {
username
:
data
.
assignee
.
username
,
username
:
data
.
assignee
.
username
,
avatar
:
data
.
assignee
.
avatar_url
avatar
:
data
.
assignee
.
avatar_url
};
};
tooltipTitle
=
_
.
escape
(
user
.
name
);
}
else
{
}
else
{
user
=
{
user
=
{
name
:
'
Unassigned
'
,
name
:
'
Unassigned
'
,
username
:
''
,
username
:
''
,
avatar
:
''
avatar
:
''
};
};
tooltipTitle
=
__
(
'
Assignee
'
);
}
}
$value
.
html
(
assigneeTemplate
(
user
));
$value
.
html
(
assigneeTemplate
(
user
));
$collapsedSidebar
.
attr
(
'
title
'
,
_
.
escape
(
user
.
name
)
).
tooltip
(
'
fixTitle
'
);
$collapsedSidebar
.
attr
(
'
title
'
,
tooltipTitle
).
tooltip
(
'
fixTitle
'
);
return
$collapsedSidebar
.
html
(
collapsedAssigneeTemplate
(
user
));
return
$collapsedSidebar
.
html
(
collapsedAssigneeTemplate
(
user
));
});
});
};
};
...
...
app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
View file @
49b85262
<
script
>
<
script
>
export
default
{
import
{
__
}
from
'
~/locale
'
;
import
tooltip
from
'
~/vue_shared/directives/tooltip
'
;
export
default
{
name
:
'
ToggleSidebar
'
,
name
:
'
ToggleSidebar
'
,
directives
:
{
tooltip
,
},
props
:
{
props
:
{
collapsed
:
{
collapsed
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
true
,
required
:
true
,
},
},
},
},
computed
:
{
tooltipLabel
()
{
return
this
.
collapsed
?
__
(
'
Expand sidebar
'
)
:
__
(
'
Collapse sidebar
'
);
},
},
methods
:
{
methods
:
{
toggle
()
{
toggle
()
{
this
.
$emit
(
'
toggle
'
);
this
.
$emit
(
'
toggle
'
);
},
},
},
},
};
};
</
script
>
</
script
>
<
template
>
<
template
>
...
@@ -20,6 +31,10 @@
...
@@ -20,6 +31,10 @@
type=
"button"
type=
"button"
class=
"btn btn-blank gutter-toggle btn-sidebar-action"
class=
"btn btn-blank gutter-toggle btn-sidebar-action"
@
click=
"toggle"
@
click=
"toggle"
v-tooltip
data-container=
"body"
data-placement=
"left"
:title=
"tooltipLabel"
>
>
<i
<i
aria-label=
"toggle collapse"
aria-label=
"toggle collapse"
...
...
app/assets/stylesheets/framework/variables.scss
View file @
49b85262
...
@@ -247,6 +247,7 @@ $btn-sm-side-margin: 7px;
...
@@ -247,6 +247,7 @@ $btn-sm-side-margin: 7px;
$btn-xs-side-margin
:
5px
;
$btn-xs-side-margin
:
5px
;
$issue-status-expired
:
$orange-500
;
$issue-status-expired
:
$orange-500
;
$issuable-sidebar-color
:
$gl-text-color-secondary
;
$issuable-sidebar-color
:
$gl-text-color-secondary
;
$sidebar-block-hover-color
:
#ebebeb
;
$group-path-color
:
#999
;
$group-path-color
:
#999
;
$namespace-kind-color
:
#aaa
;
$namespace-kind-color
:
#aaa
;
$panel-heading-link-color
:
#777
;
$panel-heading-link-color
:
#777
;
...
@@ -373,6 +374,8 @@ $dropdown-hover-color: $blue-400;
...
@@ -373,6 +374,8 @@ $dropdown-hover-color: $blue-400;
$link-active-background
:
rgba
(
0
,
0
,
0
,
0
.04
);
$link-active-background
:
rgba
(
0
,
0
,
0
,
0
.04
);
$link-hover-background
:
rgba
(
0
,
0
,
0
,
0
.06
);
$link-hover-background
:
rgba
(
0
,
0
,
0
,
0
.06
);
$inactive-badge-background
:
rgba
(
0
,
0
,
0
,
0
.08
);
$inactive-badge-background
:
rgba
(
0
,
0
,
0
,
0
.08
);
$sidebar-toggle-height
:
60px
;
$sidebar-milestone-toggle-bottom-margin
:
10px
;
/*
/*
* Buttons
* Buttons
...
...
app/assets/stylesheets/pages/issuable.scss
View file @
49b85262
...
@@ -187,7 +187,12 @@
...
@@ -187,7 +187,12 @@
padding-left
:
10px
;
padding-left
:
10px
;
&
:hover
{
&
:hover
{
color
:
$gray-darkest
;
color
:
$gl-text-color
;
}
&
:hover
,
&
:focus
{
text-decoration
:
none
;
}
}
}
}
...
@@ -368,6 +373,14 @@
...
@@ -368,6 +373,14 @@
padding
:
15px
0
0
;
padding
:
15px
0
0
;
border-bottom
:
0
;
border-bottom
:
0
;
overflow
:
hidden
;
overflow
:
hidden
;
&
:hover
{
background-color
:
$sidebar-block-hover-color
;
}
&
.issuable-sidebar-header
{
padding-top
:
0
;
}
}
}
.participants
{
.participants
{
...
@@ -380,8 +393,17 @@
...
@@ -380,8 +393,17 @@
.gutter-toggle
{
.gutter-toggle
{
width
:
100%
;
width
:
100%
;
height
:
$sidebar-toggle-height
;
margin-left
:
0
;
margin-left
:
0
;
padding-left
:
25px
;
padding-left
:
0
;
border-bottom
:
1px
solid
$border-gray-dark
;
}
a
.gutter-toggle
{
display
:
flex
;
justify-content
:
center
;
flex-direction
:
column
;
text-align
:
center
;
}
}
.sidebar-collapsed-icon
{
.sidebar-collapsed-icon
{
...
@@ -428,10 +450,10 @@
...
@@ -428,10 +450,10 @@
.btn-clipboard
{
.btn-clipboard
{
border
:
0
;
border
:
0
;
background
:
transparent
;
color
:
$issuable-sidebar-color
;
color
:
$issuable-sidebar-color
;
&
:hover
{
&
:hover
{
background
:
transparent
;
color
:
$gl-text-color
;
color
:
$gl-text-color
;
}
}
}
}
...
...
app/assets/stylesheets/pages/milestone.scss
View file @
49b85262
...
@@ -53,10 +53,6 @@
...
@@ -53,10 +53,6 @@
}
}
.milestone-sidebar
{
.milestone-sidebar
{
.gutter-toggle
{
margin-bottom
:
10px
;
}
.milestone-progress
{
.milestone-progress
{
.title
{
.title
{
padding-top
:
5px
;
padding-top
:
5px
;
...
@@ -102,7 +98,17 @@
...
@@ -102,7 +98,17 @@
margin-right
:
0
;
margin-right
:
0
;
}
}
.right-sidebar-expanded
&
{
.gutter-toggle
{
margin-bottom
:
$sidebar-milestone-toggle-bottom-margin
;
}
}
.right-sidebar-collapsed
&
{
.right-sidebar-collapsed
&
{
.milestone-progress
{
padding-top
:
0
;
}
.reference
{
.reference
{
border-top
:
1px
solid
$border-gray-normal
;
border-top
:
1px
solid
$border-gray-normal
;
}
}
...
...
app/helpers/issuables_helper.rb
View file @
49b85262
...
@@ -9,6 +9,32 @@ module IssuablesHelper
...
@@ -9,6 +9,32 @@ module IssuablesHelper
"right-sidebar-
#{
sidebar_gutter_collapsed?
?
'collapsed'
:
'expanded'
}
"
"right-sidebar-
#{
sidebar_gutter_collapsed?
?
'collapsed'
:
'expanded'
}
"
end
end
def
sidebar_gutter_tooltip_text
sidebar_gutter_collapsed?
?
_
(
'Expand sidebar'
)
:
_
(
'Collapse sidebar'
)
end
def
sidebar_assignee_tooltip_label
(
issuable
)
if
issuable
.
assignee
issuable
.
assignee
.
name
else
issuable
.
allows_multiple_assignees?
?
_
(
'Assignee(s)'
)
:
_
(
'Assignee'
)
end
end
def
sidebar_due_date_tooltip_label
(
issuable
)
if
issuable
.
due_date
"
#{
_
(
'Due date'
)
}
<br />
#{
due_date_remaining_days
(
issuable
)
}
"
else
_
(
'Due date'
)
end
end
def
due_date_remaining_days
(
issuable
)
remaining_days_in_words
=
remaining_days_in_words
(
issuable
)
"
#{
issuable
.
due_date
.
to_s
(
:medium
)
}
(
#{
remaining_days_in_words
}
)"
end
def
multi_label_name
(
current_labels
,
default_label
)
def
multi_label_name
(
current_labels
,
default_label
)
if
current_labels
&&
current_labels
.
any?
if
current_labels
&&
current_labels
.
any?
title
=
current_labels
.
first
.
try
(
:title
)
title
=
current_labels
.
first
.
try
(
:title
)
...
@@ -153,10 +179,14 @@ module IssuablesHelper
...
@@ -153,10 +179,14 @@ module IssuablesHelper
def
issuable_labels_tooltip
(
labels
,
limit:
5
)
def
issuable_labels_tooltip
(
labels
,
limit:
5
)
first
,
last
=
labels
.
partition
.
with_index
{
|
_
,
i
|
i
<
limit
}
first
,
last
=
labels
.
partition
.
with_index
{
|
_
,
i
|
i
<
limit
}
if
labels
&&
labels
.
any?
label_names
=
first
.
collect
(
&
:name
)
label_names
=
first
.
collect
(
&
:name
)
label_names
<<
"and
#{
last
.
size
}
more"
unless
last
.
empty?
label_names
<<
"and
#{
last
.
size
}
more"
unless
last
.
empty?
label_names
.
join
(
', '
)
label_names
.
join
(
', '
)
else
_
(
"Labels"
)
end
end
end
def
issuables_state_counter_text
(
issuable_type
,
state
,
display_count
)
def
issuables_state_counter_text
(
issuable_type
,
state
,
display_count
)
...
@@ -321,7 +351,7 @@ module IssuablesHelper
...
@@ -321,7 +351,7 @@ module IssuablesHelper
def
issuable_todo_button_data
(
issuable
,
todo
,
is_collapsed
)
def
issuable_todo_button_data
(
issuable
,
todo
,
is_collapsed
)
{
{
todo_text:
"Add todo"
,
todo_text:
"Add todo"
,
mark_text:
"Mark done"
,
mark_text:
"Mark
todo as
done"
,
todo_icon:
(
is_collapsed
?
icon
(
'plus-square'
)
:
nil
),
todo_icon:
(
is_collapsed
?
icon
(
'plus-square'
)
:
nil
),
mark_icon:
(
is_collapsed
?
icon
(
'check-square'
,
class:
'todo-undone'
)
:
nil
),
mark_icon:
(
is_collapsed
?
icon
(
'check-square'
,
class:
'todo-undone'
)
:
nil
),
issuable_id:
issuable
.
id
,
issuable_id:
issuable
.
id
,
...
...
app/helpers/milestones_helper.rb
View file @
49b85262
module
MilestonesHelper
module
MilestonesHelper
include
EntityDateHelper
def
milestones_filter_path
(
opts
=
{})
def
milestones_filter_path
(
opts
=
{})
if
@project
if
@project
project_milestones_path
(
@project
,
opts
)
project_milestones_path
(
@project
,
opts
)
...
@@ -72,6 +74,19 @@ module MilestonesHelper
...
@@ -72,6 +74,19 @@ module MilestonesHelper
end
end
end
end
def
milestone_progress_tooltip_text
(
milestone
)
has_issues
=
milestone
.
total_issues_count
(
current_user
)
>
0
if
has_issues
[
_
(
'Progress'
),
_
(
"%{percent}%% complete"
)
%
{
percent:
milestone
.
percent_complete
(
current_user
)
}
].
join
(
'<br />'
)
else
_
(
'Progress'
)
end
end
def
milestone_progress_bar
(
milestone
)
def
milestone_progress_bar
(
milestone
)
options
=
{
options
=
{
class:
'progress-bar progress-bar-success'
,
class:
'progress-bar progress-bar-success'
,
...
@@ -95,27 +110,69 @@ module MilestonesHelper
...
@@ -95,27 +110,69 @@ module MilestonesHelper
end
end
def
milestone_tooltip_title
(
milestone
)
def
milestone_tooltip_title
(
milestone
)
if
milestone
.
due_date
if
milestone
[
milestone
.
due_date
.
to_s
(
:medium
),
"(
#{
milestone_remaining_days
(
milestone
)
}
)"
].
join
(
' '
)
"
#{
milestone
.
title
}
<br />
#{
milestone_tooltip_due_date
(
milestone
)
}
"
else
_
(
'Milestone'
)
end
end
end
end
def
milestone_remaining_days
(
milestone
)
def
milestone_time_for
(
date
,
date_type
)
if
milestone
.
expired?
title
=
date_type
==
:start
?
"Start date"
:
"End date"
content_tag
(
:strong
,
'Past due'
)
elsif
milestone
.
upcoming?
if
date
content_tag
(
:strong
,
'Upcoming'
)
time_ago
=
time_ago_in_words
(
date
)
elsif
milestone
.
due_date
time_ago
.
slice!
(
"about "
)
time_ago
=
time_ago_in_words
(
milestone
.
due_date
)
content
=
time_ago
.
gsub
(
/\d+/
)
{
|
match
|
"<strong>
#{
match
}
</strong>"
}
time_ago
<<
if
date
.
past?
content
.
slice!
(
"about "
)
" ago"
content
<<
" remaining"
else
content
.
html_safe
" remaining"
elsif
milestone
.
start_date
&&
milestone
.
start_date
.
past?
end
days
=
milestone
.
elapsed_days
content
=
content_tag
(
:strong
,
days
)
content
=
[
content
<<
"
#{
'day'
.
pluralize
(
days
)
}
elapsed"
title
,
"<br />"
,
date
.
to_s
(
:medium
),
"(
#{
time_ago
}
)"
].
join
(
" "
)
content
.
html_safe
content
.
html_safe
else
title
end
end
def
milestone_issues_tooltip_text
(
milestone
)
issues
=
milestone
.
count_issues_by_state
(
current_user
)
return
_
(
"Issues"
)
if
issues
.
empty?
content
=
[]
content
<<
n_
(
"1 open issue"
,
"%d open issues"
,
issues
[
"opened"
])
%
issues
[
"opened"
]
if
issues
[
"opened"
]
content
<<
n_
(
"1 closed issue"
,
"%d closed issues"
,
issues
[
"closed"
])
%
issues
[
"closed"
]
if
issues
[
"closed"
]
content
.
join
(
'<br />'
).
html_safe
end
def
milestone_merge_requests_tooltip_text
(
milestone
)
merge_requests
=
milestone
.
merge_requests
return
_
(
"Merge requests"
)
if
merge_requests
.
empty?
content
=
[]
content
<<
n_
(
"1 open merge request"
,
"%d open merge requests"
,
merge_requests
.
opened
.
count
)
%
merge_requests
.
opened
.
count
if
merge_requests
.
opened
.
any?
content
<<
n_
(
"1 closed merge request"
,
"%d closed merge requests"
,
merge_requests
.
closed
.
count
)
%
merge_requests
.
closed
.
count
if
merge_requests
.
closed
.
any?
content
<<
n_
(
"1 merged merge request"
,
"%d merged merge requests"
,
merge_requests
.
merged
.
count
)
%
merge_requests
.
merged
.
count
if
merge_requests
.
merged
.
any?
content
.
join
(
'<br />'
).
html_safe
end
def
milestone_tooltip_due_date
(
milestone
)
if
milestone
.
due_date
"
#{
milestone
.
due_date
.
to_s
(
:medium
)
}
(
#{
remaining_days_in_words
(
milestone
)
}
)"
end
end
end
end
...
...
app/models/concerns/milestoneish.rb
View file @
49b85262
...
@@ -102,14 +102,14 @@ module Milestoneish
...
@@ -102,14 +102,14 @@ module Milestoneish
Gitlab
::
TimeTrackingFormatter
.
output
(
total_issue_time_estimate
)
Gitlab
::
TimeTrackingFormatter
.
output
(
total_issue_time_estimate
)
end
end
private
def
count_issues_by_state
(
user
)
def
count_issues_by_state
(
user
)
memoize_per_user
(
user
,
:count_issues_by_state
)
do
memoize_per_user
(
user
,
:count_issues_by_state
)
do
issues_visible_to_user
(
user
).
reorder
(
nil
).
group
(
:state
).
count
issues_visible_to_user
(
user
).
reorder
(
nil
).
group
(
:state
).
count
end
end
end
end
private
def
memoize_per_user
(
user
,
method_name
)
def
memoize_per_user
(
user
,
method_name
)
memoized_users
[
method_name
][
user
&
.
id
]
||=
yield
memoized_users
[
method_name
][
user
&
.
id
]
||=
yield
end
end
...
...
app/serializers/entity_date_helper.rb
View file @
49b85262
module
EntityDateHelper
module
EntityDateHelper
include
ActionView
::
Helpers
::
DateHelper
include
ActionView
::
Helpers
::
DateHelper
include
ActionView
::
Helpers
::
TagHelper
def
interval_in_words
(
diff
)
def
interval_in_words
(
diff
)
return
'Not started'
unless
diff
return
'Not started'
unless
diff
...
@@ -34,4 +35,30 @@ module EntityDateHelper
...
@@ -34,4 +35,30 @@ module EntityDateHelper
duration_hash
duration_hash
end
end
# Generates an HTML-formatted string for remaining dates based on start_date and due_date
#
# It returns "Past due" for expired entities
# It returns "Upcoming" for upcoming entities
# If due date is provided, it returns "# days|weeks|months remaining|ago"
# If start date is provided and elapsed, with no due date, it returns "# days elapsed"
def
remaining_days_in_words
(
entity
)
if
entity
.
try
(
:expired?
)
content_tag
(
:strong
,
'Past due'
)
elsif
entity
.
try
(
:upcoming?
)
content_tag
(
:strong
,
'Upcoming'
)
elsif
entity
.
due_date
is_upcoming
=
(
entity
.
due_date
-
Date
.
today
).
to_i
>
0
time_ago
=
time_ago_in_words
(
entity
.
due_date
)
content
=
time_ago
.
gsub
(
/\d+/
)
{
|
match
|
"<strong>
#{
match
}
</strong>"
}
content
.
slice!
(
"about "
)
content
<<
" "
+
(
is_upcoming
?
_
(
"remaining"
)
:
_
(
"ago"
))
content
.
html_safe
elsif
entity
.
start_date
&&
entity
.
start_date
.
past?
days
=
entity
.
elapsed_days
content
=
content_tag
(
:strong
,
days
)
content
<<
"
#{
'day'
.
pluralize
(
days
)
}
elapsed"
content
.
html_safe
end
end
end
end
app/views/projects/issues/_issue.html.haml
View file @
49b85262
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
-
if
issue
.
milestone
-
if
issue
.
milestone
%span
.issuable-milestone.hidden-xs
%span
.issuable-milestone.hidden-xs
=
link_to
project_issues_path
(
issue
.
project
,
milestone_title:
issue
.
milestone
.
title
),
data:
{
html:
1
,
toggle:
'tooltip'
,
title:
issuable_milestone_tooltip_title
(
issu
e
)
}
do
=
link_to
project_issues_path
(
issue
.
project
,
milestone_title:
issue
.
milestone
.
title
),
data:
{
html:
1
,
toggle:
'tooltip'
,
title:
milestone_tooltip_due_date
(
issue
.
mileston
e
)
}
do
=
icon
(
'clock-o'
)
=
icon
(
'clock-o'
)
=
issue
.
milestone
.
title
=
issue
.
milestone
.
title
-
if
issue
.
due_date
-
if
issue
.
due_date
...
...
app/views/projects/merge_requests/_merge_request.html.haml
View file @
49b85262
...
@@ -23,7 +23,7 @@
...
@@ -23,7 +23,7 @@
-
if
merge_request
.
milestone
-
if
merge_request
.
milestone
%span
.issuable-milestone.hidden-xs
%span
.issuable-milestone.hidden-xs
=
link_to
project_merge_requests_path
(
merge_request
.
project
,
milestone_title:
merge_request
.
milestone
.
title
),
data:
{
html:
1
,
toggle:
'tooltip'
,
title:
issuable_milestone_tooltip_title
(
merge_request
)
}
do
=
link_to
project_merge_requests_path
(
merge_request
.
project
,
milestone_title:
merge_request
.
milestone
.
title
),
data:
{
html:
1
,
toggle:
'tooltip'
,
title:
milestone_tooltip_due_date
(
merge_request
.
milestone
)
}
do
=
icon
(
'clock-o'
)
=
icon
(
'clock-o'
)
=
merge_request
.
milestone
.
title
=
merge_request
.
milestone
.
title
-
if
merge_request
.
target_project
.
default_branch
!=
merge_request
.
target_branch
-
if
merge_request
.
target_project
.
default_branch
!=
merge_request
.
target_branch
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
49b85262
...
@@ -7,7 +7,7 @@
...
@@ -7,7 +7,7 @@
-
if
current_user
-
if
current_user
%span
.issuable-header-text.hide-collapsed.pull-left
%span
.issuable-header-text.hide-collapsed.pull-left
=
_
(
'Todo'
)
=
_
(
'Todo'
)
%a
.gutter-toggle.pull-right.js-sidebar-toggle
{
role:
"button"
,
href:
"#"
,
"aria-label"
=>
"Toggle sidebar"
}
%a
.gutter-toggle.pull-right.js-sidebar-toggle
.has-tooltip
{
role:
"button"
,
href:
"#"
,
"aria-label"
=>
"Toggle sidebar"
,
title:
sidebar_gutter_tooltip_text
,
data:
{
container:
'body'
,
placement:
'left'
}
}
=
sidebar_gutter_toggle_icon
=
sidebar_gutter_toggle_icon
-
if
current_user
-
if
current_user
=
render
"shared/issuable/sidebar_todo"
,
todo:
todo
,
issuable:
issuable
=
render
"shared/issuable/sidebar_todo"
,
todo:
todo
,
issuable:
issuable
...
@@ -19,11 +19,10 @@
...
@@ -19,11 +19,10 @@
.block.assignee
.block.assignee
=
render
"shared/issuable/sidebar_assignees"
,
issuable:
issuable
,
can_edit_issuable:
can_edit_issuable
,
signed_in:
current_user
.
present?
=
render
"shared/issuable/sidebar_assignees"
,
issuable:
issuable
,
can_edit_issuable:
can_edit_issuable
,
signed_in:
current_user
.
present?
.block.milestone
.block.milestone
.sidebar-collapsed-icon
.sidebar-collapsed-icon
.has-tooltip
{
title:
milestone_tooltip_title
(
issuable
.
milestone
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
icon
(
'clock-o'
,
'aria-hidden'
:
'true'
)
=
icon
(
'clock-o'
,
'aria-hidden'
:
'true'
)
%span
.milestone-title
%span
.milestone-title
-
if
issuable
.
milestone
-
if
issuable
.
milestone
%span
.has-tooltip
{
title:
"#{issuable.milestone.title}<br>#{milestone_tooltip_title(issuable.milestone)}"
,
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
issuable
.
milestone
.
title
=
issuable
.
milestone
.
title
-
else
-
else
=
_
(
'None'
)
=
_
(
'None'
)
...
@@ -34,7 +33,7 @@
...
@@ -34,7 +33,7 @@
=
link_to
_
(
'Edit'
),
'#'
,
class:
'js-sidebar-dropdown-toggle edit-link pull-right'
=
link_to
_
(
'Edit'
),
'#'
,
class:
'js-sidebar-dropdown-toggle edit-link pull-right'
.value.hide-collapsed
.value.hide-collapsed
-
if
issuable
.
milestone
-
if
issuable
.
milestone
=
link_to
issuable
.
milestone
.
title
,
milestone_path
(
issuable
.
milestone
),
class:
"bold has-tooltip"
,
title:
milestone_tooltip_
titl
e
(
issuable
.
milestone
),
data:
{
container:
"body"
,
html:
1
}
=
link_to
issuable
.
milestone
.
title
,
milestone_path
(
issuable
.
milestone
),
class:
"bold has-tooltip"
,
title:
milestone_tooltip_
due_dat
e
(
issuable
.
milestone
),
data:
{
container:
"body"
,
html:
1
}
-
else
-
else
%span
.no-value
%span
.no-value
=
_
(
'None'
)
=
_
(
'None'
)
...
@@ -50,7 +49,7 @@
...
@@ -50,7 +49,7 @@
=
icon
(
'spinner spin'
,
'aria-hidden'
:
'true'
)
=
icon
(
'spinner spin'
,
'aria-hidden'
:
'true'
)
-
if
issuable
.
has_attribute?
(
:due_date
)
-
if
issuable
.
has_attribute?
(
:due_date
)
.block.due_date
.block.due_date
.sidebar-collapsed-icon
.sidebar-collapsed-icon
.has-tooltip
{
data:
{
placement:
'left'
,
container:
'body'
,
html:
1
},
title:
sidebar_due_date_tooltip_label
(
issuable
)
}
=
icon
(
'calendar'
,
'aria-hidden'
:
'true'
)
=
icon
(
'calendar'
,
'aria-hidden'
:
'true'
)
%span
.js-due-date-sidebar-value
%span
.js-due-date-sidebar-value
=
issuable
.
due_date
.
try
(
:to_s
,
:medium
)
||
'None'
=
issuable
.
due_date
.
try
(
:to_s
,
:medium
)
||
'None'
...
...
app/views/shared/issuable/_sidebar_assignees.html.haml
View file @
49b85262
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
=
_
(
'Assignee'
)
=
_
(
'Assignee'
)
=
icon
(
'spinner spin'
)
=
icon
(
'spinner spin'
)
-
else
-
else
.sidebar-collapsed-icon.sidebar-collapsed-user
{
data:
{
toggle:
"tooltip"
,
placement:
"left"
,
container:
"body"
},
title:
(
issuable
.
assignee
.
name
if
issuable
.
assigne
e
)
}
.sidebar-collapsed-icon.sidebar-collapsed-user
{
data:
{
toggle:
"tooltip"
,
placement:
"left"
,
container:
"body"
},
title:
sidebar_assignee_tooltip_label
(
issuabl
e
)
}
-
if
issuable
.
assignee
-
if
issuable
.
assignee
=
link_to_member
(
@project
,
issuable
.
assignee
,
size:
24
)
=
link_to_member
(
@project
,
issuable
.
assignee
,
size:
24
)
-
else
-
else
...
...
app/views/shared/issuable/_sidebar_todo.html.haml
View file @
49b85262
-
is_collapsed
=
local_assigns
.
fetch
(
:is_collapsed
,
false
)
-
is_collapsed
=
local_assigns
.
fetch
(
:is_collapsed
,
false
)
-
mark_content
=
is_collapsed
?
icon
(
'check-square'
,
class:
'todo-undone'
)
:
_
(
'Mark done'
)
-
mark_content
=
is_collapsed
?
icon
(
'check-square'
,
class:
'todo-undone'
)
:
_
(
'Mark
todo as
done'
)
-
todo_content
=
is_collapsed
?
icon
(
'plus-square'
)
:
_
(
'Add todo'
)
-
todo_content
=
is_collapsed
?
icon
(
'plus-square'
)
:
_
(
'Add todo'
)
%button
.issuable-todo-btn.js-issuable-todo
{
type:
'button'
,
%button
.issuable-todo-btn.js-issuable-todo
{
type:
'button'
,
class:
(
is_collapsed
?
'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip'
:
'btn btn-default issuable-header-btn pull-right'
),
class:
(
is_collapsed
?
'btn-blank sidebar-collapsed-icon dont-change-state has-tooltip'
:
'btn btn-default issuable-header-btn pull-right'
),
title:
(
todo
.
nil?
?
_
(
'Add todo'
)
:
_
(
'Mark done'
)),
title:
(
todo
.
nil?
?
_
(
'Add todo'
)
:
_
(
'Mark
todo as
done'
)),
'aria-label'
=>
(
todo
.
nil?
?
_
(
'Add todo'
)
:
_
(
'Mark done'
)),
'aria-label'
=>
(
todo
.
nil?
?
_
(
'Add todo'
)
:
_
(
'Mark
todo as
done'
)),
data:
issuable_todo_button_data
(
issuable
,
todo
,
is_collapsed
)
}
data:
issuable_todo_button_data
(
issuable
,
todo
,
is_collapsed
)
}
%span
.issuable-todo-inner.js-issuable-todo-inner
<
%span
.issuable-todo-inner.js-issuable-todo-inner
<
-
if
todo
-
if
todo
...
...
app/views/shared/issuable/form/_merge_request_assignee.html.haml
View file @
49b85262
-
merge_request
=
issuable
-
merge_request
=
issuable
.block.assignee
.block.assignee
.sidebar-collapsed-icon.sidebar-collapsed-user
{
data:
{
toggle:
"tooltip"
,
placement:
"left"
,
container:
"body"
},
title:
(
merge_request
.
assignee
.
name
if
merge_request
.
assigne
e
)
}
.sidebar-collapsed-icon.sidebar-collapsed-user
{
data:
{
toggle:
"tooltip"
,
placement:
"left"
,
container:
"body"
},
title:
sidebar_assignee_tooltip_label
(
issuabl
e
)
}
-
if
merge_request
.
assignee
-
if
merge_request
.
assignee
=
link_to_member
(
@project
,
merge_request
.
assignee
,
size:
24
)
=
link_to_member
(
@project
,
merge_request
.
assignee
,
size:
24
)
-
else
-
else
...
...
app/views/shared/milestones/_sidebar.html.haml
View file @
49b85262
...
@@ -4,12 +4,8 @@
...
@@ -4,12 +4,8 @@
%aside
.right-sidebar.js-right-sidebar
{
data:
{
"offset-top"
=>
affix_offset
,
"spy"
=>
"affix"
,
"always-show-toggle"
=>
true
},
class:
sidebar_gutter_collapsed_class
,
'aria-live'
=>
'polite'
}
%aside
.right-sidebar.js-right-sidebar
{
data:
{
"offset-top"
=>
affix_offset
,
"spy"
=>
"affix"
,
"always-show-toggle"
=>
true
},
class:
sidebar_gutter_collapsed_class
,
'aria-live'
=>
'polite'
}
.issuable-sidebar.milestone-sidebar
.issuable-sidebar.milestone-sidebar
.block.milestone-progress.issuable-sidebar-header
.block.milestone-progress.issuable-sidebar-header
%a
.gutter-toggle.pull-right.js-sidebar-toggle
{
role:
"button"
,
href:
"#"
,
"aria-label"
=>
"Toggle sidebar"
}
%a
.gutter-toggle.pull-right.js-sidebar-toggle
.has-tooltip
{
role:
"button"
,
href:
"#"
,
"aria-label"
=>
"Toggle sidebar"
,
title:
sidebar_gutter_tooltip_text
,
data:
{
container:
'body'
,
placement:
'left'
}
}
=
sidebar_gutter_toggle_icon
=
sidebar_gutter_toggle_icon
.sidebar-collapsed-icon
%span
==
#{
milestone
.
percent_complete
(
current_user
)
}
%
=
milestone_progress_bar
(
milestone
)
.title.hide-collapsed
.title.hide-collapsed
%strong
.bold
==
#{
milestone
.
percent_complete
(
current_user
)
}
%
%strong
.bold
==
#{
milestone
.
percent_complete
(
current_user
)
}
%
%span
.hide-collapsed
%span
.hide-collapsed
...
@@ -17,6 +13,11 @@
...
@@ -17,6 +13,11 @@
.value.hide-collapsed
.value.hide-collapsed
=
milestone_progress_bar
(
milestone
)
=
milestone_progress_bar
(
milestone
)
.block.milestone-progress.hide-expanded
.sidebar-collapsed-icon.has-tooltip
{
title:
milestone_progress_tooltip_text
(
milestone
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
%span
==
#{
milestone
.
percent_complete
(
current_user
)
}
%
=
milestone_progress_bar
(
milestone
)
.block.start_date.hide-collapsed
.block.start_date.hide-collapsed
.title
.title
Start date
Start date
...
@@ -35,18 +36,24 @@
...
@@ -35,18 +36,24 @@
%span
.collapsed-milestone-date
%span
.collapsed-milestone-date
-
if
milestone
.
start_date
&&
milestone
.
due_date
-
if
milestone
.
start_date
&&
milestone
.
due_date
-
if
milestone
.
start_date
.
year
==
milestone
.
due_date
.
year
-
if
milestone
.
start_date
.
year
==
milestone
.
due_date
.
year
.milestone-date
=
milestone
.
start_date
.
strftime
(
'%b %-d'
)
.milestone-date.has-tooltip
{
title:
milestone_time_for
(
milestone
.
start_date
,
:start
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
milestone
.
start_date
.
strftime
(
'%b %-d'
)
-
else
-
else
.milestone-date
=
milestone
.
start_date
.
strftime
(
'%b %-d %Y'
)
.milestone-date.has-tooltip
{
title:
milestone_time_for
(
milestone
.
start_date
,
:start
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
milestone
.
start_date
.
strftime
(
'%b %-d %Y'
)
.date-separator
-
.date-separator
-
.due_date
=
milestone
.
due_date
.
strftime
(
'%b %-d %Y'
)
.due_date.has-tooltip
{
title:
milestone_time_for
(
milestone
.
due_date
,
:end
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
milestone
.
due_date
.
strftime
(
'%b %-d %Y'
)
-
elsif
milestone
.
start_date
-
elsif
milestone
.
start_date
From
From
.milestone-date
=
milestone
.
start_date
.
strftime
(
'%b %-d %Y'
)
.milestone-date.has-tooltip
{
title:
milestone_time_for
(
milestone
.
start_date
,
:start
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
milestone
.
start_date
.
strftime
(
'%b %-d %Y'
)
-
elsif
milestone
.
due_date
-
elsif
milestone
.
due_date
Until
Until
.milestone-date
=
milestone
.
due_date
.
strftime
(
'%b %-d %Y'
)
.milestone-date.has-tooltip
{
title:
milestone_time_for
(
milestone
.
due_date
,
:end
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
=
milestone
.
due_date
.
strftime
(
'%b %-d %Y'
)
-
else
-
else
.has-tooltip
{
title:
milestone_time_for
(
milestone
.
start_date
,
:start
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
None
None
.title.hide-collapsed
.title.hide-collapsed
Due date
Due date
...
@@ -58,14 +65,14 @@
...
@@ -58,14 +65,14 @@
%span
.bold
=
milestone
.
due_date
.
to_s
(
:medium
)
%span
.bold
=
milestone
.
due_date
.
to_s
(
:medium
)
-
else
-
else
%span
.no-value
No due date
%span
.no-value
No due date
-
remaining_days
=
milestone_remaining_day
s
(
milestone
)
-
remaining_days
=
remaining_days_in_word
s
(
milestone
)
-
if
remaining_days
.
present?
-
if
remaining_days
.
present?
=
surround
'('
,
')'
do
=
surround
'('
,
')'
do
%span
.remaining-days
=
remaining_days
%span
.remaining-days
=
remaining_days
-
if
!
project
||
can?
(
current_user
,
:read_issue
,
project
)
-
if
!
project
||
can?
(
current_user
,
:read_issue
,
project
)
.block.issues
.block.issues
.sidebar-collapsed-icon
.sidebar-collapsed-icon
.has-tooltip
{
title:
milestone_issues_tooltip_text
(
milestone
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
%strong
%strong
=
custom_icon
(
'issues'
)
=
custom_icon
(
'issues'
)
%span
=
milestone
.
issues_visible_to_user
(
current_user
).
count
%span
=
milestone
.
issues_visible_to_user
(
current_user
).
count
...
@@ -93,7 +100,7 @@
...
@@ -93,7 +100,7 @@
=
icon
(
'spinner spin'
)
=
icon
(
'spinner spin'
)
.block.merge-requests
.block.merge-requests
.sidebar-collapsed-icon
.sidebar-collapsed-icon
.has-tooltip
{
title:
milestone_merge_requests_tooltip_text
(
milestone
),
data:
{
container:
'body'
,
html:
1
,
placement:
'left'
}
}
%strong
%strong
=
custom_icon
(
'mr_bold'
)
=
custom_icon
(
'mr_bold'
)
%span
=
milestone
.
merge_requests
.
count
%span
=
milestone
.
merge_requests
.
count
...
...
changelogs/unreleased/25010-collapsed-sidebar-tooltips.yml
0 → 100644
View file @
49b85262
---
title
:
Improve tooltips in collapsed right sidebar
merge_request
:
17714
author
:
type
:
changed
doc/user/project/issues/issues_functionalities.md
View file @
49b85262
...
@@ -28,7 +28,7 @@ Comments and system notes also appear automatically in response to various actio
...
@@ -28,7 +28,7 @@ Comments and system notes also appear automatically in response to various actio
#### 2. Todos
#### 2. Todos
-
Add todo: add that issue to your
[
GitLab Todo
](
../../../workflow/todos.html
)
list
-
Add todo: add that issue to your
[
GitLab Todo
](
../../../workflow/todos.html
)
list
-
Mark done: mark that issue as done (reflects on the Todo list)
-
Mark
todo as
done: mark that issue as done (reflects on the Todo list)
#### 3. Assignee
#### 3. Assignee
...
...
doc/workflow/todos.md
View file @
49b85262
...
@@ -92,9 +92,9 @@ corresponding **Done** button, and it will disappear from your Todo list.
...
@@ -92,9 +92,9 @@ corresponding **Done** button, and it will disappear from your Todo list.
![
A Todo in the Todos dashboard
](
img/todo_list_item.png
)
![
A Todo in the Todos dashboard
](
img/todo_list_item.png
)
A Todo can also be marked as done from the issue or merge request sidebar using
A Todo can also be marked as done from the issue or merge request sidebar using
the "Mark done" button.
the "Mark
todo as
done" button.
![
Mark
D
one from the issuable sidebar
](
img/todos_mark_done_sidebar.png
)
![
Mark
todo as d
one from the issuable sidebar
](
img/todos_mark_done_sidebar.png
)
You can mark all your Todos as done at once by clicking on the
**
Mark all as
You can mark all your Todos as done at once by clicking on the
**
Mark all as
done
**
button.
done
**
button.
...
...
spec/features/issues/todo_spec.rb
View file @
49b85262
...
@@ -14,7 +14,7 @@ feature 'Manually create a todo item from issue', :js do
...
@@ -14,7 +14,7 @@ feature 'Manually create a todo item from issue', :js do
it
'creates todo when clicking button'
do
it
'creates todo when clicking button'
do
page
.
within
'.issuable-sidebar'
do
page
.
within
'.issuable-sidebar'
do
click_button
'Add todo'
click_button
'Add todo'
expect
(
page
).
to
have_content
'Mark done'
expect
(
page
).
to
have_content
'Mark
todo as
done'
end
end
page
.
within
'.header-content .todos-count'
do
page
.
within
'.header-content .todos-count'
do
...
@@ -31,7 +31,7 @@ feature 'Manually create a todo item from issue', :js do
...
@@ -31,7 +31,7 @@ feature 'Manually create a todo item from issue', :js do
it
'marks a todo as done'
do
it
'marks a todo as done'
do
page
.
within
'.issuable-sidebar'
do
page
.
within
'.issuable-sidebar'
do
click_button
'Add todo'
click_button
'Add todo'
click_button
'Mark done'
click_button
'Mark
todo as
done'
end
end
expect
(
page
).
to
have_selector
(
'.todos-count'
,
visible:
false
)
expect
(
page
).
to
have_selector
(
'.todos-count'
,
visible:
false
)
...
...
spec/helpers/issuables_helper_spec.rb
View file @
49b85262
...
@@ -22,11 +22,15 @@ describe IssuablesHelper do
...
@@ -22,11 +22,15 @@ describe IssuablesHelper do
end
end
describe
'#issuable_labels_tooltip'
do
describe
'#issuable_labels_tooltip'
do
it
'returns label text'
do
it
'returns label text with no labels'
do
expect
(
issuable_labels_tooltip
([])).
to
eq
(
"Labels"
)
end
it
'returns label text with labels within max limit'
do
expect
(
issuable_labels_tooltip
([
label
])).
to
eq
(
label
.
title
)
expect
(
issuable_labels_tooltip
([
label
])).
to
eq
(
label
.
title
)
end
end
it
'returns label text'
do
it
'returns label text
with labels exceeding max limit
'
do
expect
(
issuable_labels_tooltip
([
label
,
label2
],
limit:
1
)).
to
eq
(
"
#{
label
.
title
}
, and 1 more"
)
expect
(
issuable_labels_tooltip
([
label
,
label2
],
limit:
1
)).
to
eq
(
"
#{
label
.
title
}
, and 1 more"
)
end
end
end
end
...
...
spec/helpers/milestones_helper_spec.rb
View file @
49b85262
...
@@ -83,58 +83,4 @@ describe MilestonesHelper do
...
@@ -83,58 +83,4 @@ describe MilestonesHelper do
end
end
end
end
end
end
describe
'#milestone_remaining_days'
do
around
do
|
example
|
Timecop
.
freeze
(
Time
.
utc
(
2017
,
3
,
17
))
{
example
.
run
}
end
context
'when less than 31 days remaining'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
due_date:
12
.
days
.
from_now
.
utc
))
}
it
'returns days remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>12</strong> days remaining"
)
end
end
context
'when less than 1 year and more than 30 days remaining'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
due_date:
2
.
months
.
from_now
.
utc
))
}
it
'returns months remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>2</strong> months remaining"
)
end
end
context
'when more than 1 year remaining'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
due_date:
(
1
.
year
.
from_now
+
2
.
days
).
utc
))
}
it
'returns years remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>1</strong> year remaining"
)
end
end
context
'when milestone is expired'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
due_date:
2
.
days
.
ago
.
utc
))
}
it
'returns "Past due"'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>Past due</strong>"
)
end
end
context
'when milestone has start_date in the future'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
start_date:
2
.
days
.
from_now
.
utc
))
}
it
'returns "Upcoming"'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>Upcoming</strong>"
)
end
end
context
'when milestone has start_date in the past'
do
let
(
:milestone_remaining
)
{
milestone_remaining_days
(
build_stubbed
(
:milestone
,
start_date:
2
.
days
.
ago
.
utc
))
}
it
'returns days elapsed'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>2</strong> days elapsed"
)
end
end
end
end
end
spec/javascripts/collapsed_sidebar_todo_spec.js
View file @
49b85262
...
@@ -85,7 +85,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
...
@@ -85,7 +85,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
expect
(
document
.
querySelector
(
'
.issuable-sidebar-header .js-issuable-todo
'
).
textContent
.
trim
(),
document
.
querySelector
(
'
.issuable-sidebar-header .js-issuable-todo
'
).
textContent
.
trim
(),
).
toBe
(
'
Mark done
'
);
).
toBe
(
'
Mark
todo as
done
'
);
done
();
done
();
});
});
...
@@ -97,7 +97,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
...
@@ -97,7 +97,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
data-original-title
'
),
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
data-original-title
'
),
).
toBe
(
'
Mark done
'
);
).
toBe
(
'
Mark
todo as
done
'
);
done
();
done
();
});
});
...
@@ -128,13 +128,13 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
...
@@ -128,13 +128,13 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
.
catch
(
done
.
fail
);
.
catch
(
done
.
fail
);
});
});
it
(
'
updates aria-label to mark done
'
,
(
done
)
=>
{
it
(
'
updates aria-label to mark
todo as
done
'
,
(
done
)
=>
{
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
setTimeout
(()
=>
{
setTimeout
(()
=>
{
expect
(
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
aria-label
'
),
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
aria-label
'
),
).
toBe
(
'
Mark done
'
);
).
toBe
(
'
Mark
todo as
done
'
);
done
();
done
();
});
});
...
@@ -147,7 +147,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
...
@@ -147,7 +147,7 @@ describe('Issuable right sidebar collapsed todo toggle', () => {
.
then
(()
=>
{
.
then
(()
=>
{
expect
(
expect
(
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
aria-label
'
),
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
getAttribute
(
'
aria-label
'
),
).
toBe
(
'
Mark done
'
);
).
toBe
(
'
Mark
todo as
done
'
);
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
document
.
querySelector
(
'
.js-issuable-todo.sidebar-collapsed-icon
'
).
click
();
})
})
...
...
spec/models/milestone_spec.rb
View file @
49b85262
...
@@ -96,7 +96,9 @@ describe Milestone do
...
@@ -96,7 +96,9 @@ describe Milestone do
allow
(
milestone
).
to
receive
(
:due_date
).
and_return
(
Date
.
today
.
prev_year
)
allow
(
milestone
).
to
receive
(
:due_date
).
and_return
(
Date
.
today
.
prev_year
)
end
end
it
{
expect
(
milestone
.
expired?
).
to
be_truthy
}
it
'returns true when due_date is in the past'
do
expect
(
milestone
.
expired?
).
to
be_truthy
end
end
end
context
"not expired"
do
context
"not expired"
do
...
@@ -104,17 +106,19 @@ describe Milestone do
...
@@ -104,17 +106,19 @@ describe Milestone do
allow
(
milestone
).
to
receive
(
:due_date
).
and_return
(
Date
.
today
.
next_year
)
allow
(
milestone
).
to
receive
(
:due_date
).
and_return
(
Date
.
today
.
next_year
)
end
end
it
{
expect
(
milestone
.
expired?
).
to
be_falsey
}
it
'returns false when due_date is in the future'
do
expect
(
milestone
.
expired?
).
to
be_falsey
end
end
end
end
end
describe
'#upcoming?'
do
describe
'#upcoming?'
do
it
'returns true'
do
it
'returns true
when start_date is in the future
'
do
milestone
=
build
(
:milestone
,
start_date:
Time
.
now
+
1
.
month
)
milestone
=
build
(
:milestone
,
start_date:
Time
.
now
+
1
.
month
)
expect
(
milestone
.
upcoming?
).
to
be_truthy
expect
(
milestone
.
upcoming?
).
to
be_truthy
end
end
it
'returns false'
do
it
'returns false
when start_date is in the past
'
do
milestone
=
build
(
:milestone
,
start_date:
Date
.
today
.
prev_year
)
milestone
=
build
(
:milestone
,
start_date:
Date
.
today
.
prev_year
)
expect
(
milestone
.
upcoming?
).
to
be_falsey
expect
(
milestone
.
upcoming?
).
to
be_falsey
end
end
...
...
spec/serializers/entity_date_helper_spec.rb
View file @
49b85262
...
@@ -32,6 +32,7 @@ describe EntityDateHelper do
...
@@ -32,6 +32,7 @@ describe EntityDateHelper do
end
end
it
'converts 86560 seconds'
do
it
'converts 86560 seconds'
do
Rails
.
logger
.
debug
date_helper_class
.
inspect
expect
(
date_helper_class
.
distance_of_time_as_hash
(
86560
)).
to
eq
(
days:
1
,
mins:
2
,
seconds:
40
)
expect
(
date_helper_class
.
distance_of_time_as_hash
(
86560
)).
to
eq
(
days:
1
,
mins:
2
,
seconds:
40
)
end
end
...
@@ -42,4 +43,58 @@ describe EntityDateHelper do
...
@@ -42,4 +43,58 @@ describe EntityDateHelper do
it
'converts 986760 seconds'
do
it
'converts 986760 seconds'
do
expect
(
date_helper_class
.
distance_of_time_as_hash
(
986760
)).
to
eq
(
days:
11
,
hours:
10
,
mins:
6
)
expect
(
date_helper_class
.
distance_of_time_as_hash
(
986760
)).
to
eq
(
days:
11
,
hours:
10
,
mins:
6
)
end
end
describe
'#remaining_days_in_words'
do
around
do
|
example
|
Timecop
.
freeze
(
Time
.
utc
(
2017
,
3
,
17
))
{
example
.
run
}
end
context
'when less than 31 days remaining'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
due_date:
12
.
days
.
from_now
.
utc
))
}
it
'returns days remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>12</strong> days remaining"
)
end
end
context
'when less than 1 year and more than 30 days remaining'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
due_date:
2
.
months
.
from_now
.
utc
))
}
it
'returns months remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>2</strong> months remaining"
)
end
end
context
'when more than 1 year remaining'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
due_date:
(
1
.
year
.
from_now
+
2
.
days
).
utc
))
}
it
'returns years remaining'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>1</strong> year remaining"
)
end
end
context
'when milestone is expired'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
due_date:
2
.
days
.
ago
.
utc
))
}
it
'returns "Past due"'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>Past due</strong>"
)
end
end
context
'when milestone has start_date in the future'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
start_date:
2
.
days
.
from_now
.
utc
))
}
it
'returns "Upcoming"'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>Upcoming</strong>"
)
end
end
context
'when milestone has start_date in the past'
do
let
(
:milestone_remaining
)
{
date_helper_class
.
remaining_days_in_words
(
build_stubbed
(
:milestone
,
start_date:
2
.
days
.
ago
.
utc
))
}
it
'returns days elapsed'
do
expect
(
milestone_remaining
).
to
eq
(
"<strong>2</strong> days elapsed"
)
end
end
end
end
end
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