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
dd5a87ca
Commit
dd5a87ca
authored
Jul 07, 2021
by
Florie Guibert
Committed by
Natalia Tepluhina
Jul 07, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Milestone widget for Issue and MR page sidebars [RUN AS-IF-FOSS]
parent
c2c448c0
Changes
14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
165 additions
and
72 deletions
+165
-72
app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
...avascripts/sidebar/components/sidebar_dropdown_widget.vue
+23
-4
app/assets/javascripts/sidebar/constants.js
app/assets/javascripts/sidebar/constants.js
+9
-0
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+39
-1
app/assets/javascripts/sidebar/queries/merge_request_milestone.query.graphql
...pts/sidebar/queries/merge_request_milestone.query.graphql
+14
-0
app/assets/javascripts/sidebar/queries/milestone.fragment.graphql
...ts/javascripts/sidebar/queries/milestone.fragment.graphql
+1
-0
app/assets/javascripts/sidebar/queries/project_issue_milestone.mutation.graphql
.../sidebar/queries/project_issue_milestone.mutation.graphql
+1
-0
app/assets/javascripts/sidebar/queries/project_milestones.query.graphql
...ascripts/sidebar/queries/project_milestones.query.graphql
+7
-1
app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql
...r/queries/update_merge_request_milestone.mutation.graphql
+17
-0
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+1
-24
locale/gitlab.pot
locale/gitlab.pot
+3
-3
qa/qa/page/component/issuable/sidebar.rb
qa/qa/page/component/issuable/sidebar.rb
+11
-5
spec/features/issues/issue_sidebar_spec.rb
spec/features/issues/issue_sidebar_spec.rb
+14
-16
spec/features/issues/user_edits_issue_spec.rb
spec/features/issues/user_edits_issue_spec.rb
+22
-18
spec/frontend/sidebar/mock_data.js
spec/frontend/sidebar/mock_data.js
+3
-0
No files found.
app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
View file @
dd5a87ca
...
@@ -29,6 +29,7 @@ export default {
...
@@ -29,6 +29,7 @@ export default {
issuableAttributesQueries
,
issuableAttributesQueries
,
i18n
:
{
i18n
:
{
[
IssuableAttributeType
.
Milestone
]:
__
(
'
Milestone
'
),
[
IssuableAttributeType
.
Milestone
]:
__
(
'
Milestone
'
),
expired
:
__
(
'
(expired)
'
),
none
:
__
(
'
None
'
),
none
:
__
(
'
None
'
),
},
},
directives
:
{
directives
:
{
...
@@ -74,9 +75,14 @@ export default {
...
@@ -74,9 +75,14 @@ export default {
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
validator
(
value
)
{
validator
(
value
)
{
return
value
===
IssuableType
.
Issue
;
return
[
IssuableType
.
Issue
,
IssuableType
.
MergeRequest
].
includes
(
value
)
;
},
},
},
},
icon
:
{
type
:
String
,
required
:
false
,
default
:
undefined
,
},
},
},
apollo
:
{
apollo
:
{
currentAttribute
:
{
currentAttribute
:
{
...
@@ -172,6 +178,9 @@ export default {
...
@@ -172,6 +178,9 @@ export default {
attributeTypeTitle
()
{
attributeTypeTitle
()
{
return
this
.
$options
.
i18n
[
this
.
issuableAttribute
];
return
this
.
$options
.
i18n
[
this
.
issuableAttribute
];
},
},
attributeTypeIcon
()
{
return
this
.
icon
||
this
.
issuableAttribute
;
},
i18n
()
{
i18n
()
{
return
{
return
{
noAttribute
:
sprintf
(
s__
(
'
DropdownWidget|No %{issuableAttribute}
'
),
{
noAttribute
:
sprintf
(
s__
(
'
DropdownWidget|No %{issuableAttribute}
'
),
{
...
@@ -224,7 +233,8 @@ export default {
...
@@ -224,7 +233,8 @@ export default {
variables
:
{
variables
:
{
fullPath
:
this
.
workspacePath
,
fullPath
:
this
.
workspacePath
,
attributeId
:
attributeId
:
this
.
issuableAttribute
===
IssuableAttributeType
.
Milestone
this
.
issuableAttribute
===
IssuableAttributeType
.
Milestone
&&
this
.
issuableType
===
IssuableType
.
Issue
?
getIdFromGraphQLId
(
attributeId
)
?
getIdFromGraphQLId
(
attributeId
)
:
attributeId
,
:
attributeId
,
iid
:
this
.
iid
,
iid
:
this
.
iid
,
...
@@ -255,6 +265,11 @@ export default {
...
@@ -255,6 +265,11 @@ export default {
attributeId
===
this
.
currentAttribute
?.
id
||
(
!
this
.
currentAttribute
?.
id
&&
!
attributeId
)
attributeId
===
this
.
currentAttribute
?.
id
||
(
!
this
.
currentAttribute
?.
id
&&
!
attributeId
)
);
);
},
},
isAttributeOverdue
(
attribute
)
{
return
this
.
issuableAttribute
===
IssuableAttributeType
.
Milestone
?
attribute
?.
expired
:
false
;
},
showDropdown
()
{
showDropdown
()
{
this
.
$refs
.
newDropdown
.
show
();
this
.
$refs
.
newDropdown
.
show
();
},
},
...
@@ -284,8 +299,10 @@ export default {
...
@@ -284,8 +299,10 @@ export default {
>
>
<template
#collapsed
>
<template
#collapsed
>
<div
v-if=
"isClassicSidebar"
v-gl-tooltip
class=
"sidebar-collapsed-icon"
>
<div
v-if=
"isClassicSidebar"
v-gl-tooltip
class=
"sidebar-collapsed-icon"
>
<gl-icon
:size=
"16"
:aria-label=
"attributeTypeTitle"
:name=
"issuableAttribute"
/>
<gl-icon
:size=
"16"
:aria-label=
"attributeTypeTitle"
:name=
"attributeTypeIcon"
/>
<span
class=
"collapse-truncated-title"
>
{{
attributeTitle
}}
</span>
<span
class=
"collapse-truncated-title"
>
{{
attributeTitle
}}
</span>
</div>
</div>
<div
<div
:data-testid=
"`select-$
{issuableAttribute}`"
:data-testid=
"`select-$
{issuableAttribute}`"
...
@@ -308,6 +325,7 @@ export default {
...
@@ -308,6 +325,7 @@ export default {
:data-qa-selector=
"`$
{issuableAttribute}_link`"
:data-qa-selector=
"`$
{issuableAttribute}_link`"
>
>
{{
attributeTitle
}}
{{
attributeTitle
}}
<span
v-if=
"isAttributeOverdue(currentAttribute)"
>
{{
$options
.
i18n
.
expired
}}
</span>
</gl-link>
</gl-link>
</slot>
</slot>
</div>
</div>
...
@@ -357,6 +375,7 @@ export default {
...
@@ -357,6 +375,7 @@ export default {
@click="updateAttribute(attrItem.id)"
@click="updateAttribute(attrItem.id)"
>
>
{{
attrItem
.
title
}}
{{
attrItem
.
title
}}
<span
v-if=
"isAttributeOverdue(attrItem)"
>
{{
$options
.
i18n
.
expired
}}
</span>
</gl-dropdown-item>
</gl-dropdown-item>
</slot>
</slot>
</
template
>
</
template
>
...
...
app/assets/javascripts/sidebar/constants.js
View file @
dd5a87ca
...
@@ -12,6 +12,7 @@ import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
...
@@ -12,6 +12,7 @@ import issueDueDateQuery from '~/sidebar/queries/issue_due_date.query.graphql';
import
issueReferenceQuery
from
'
~/sidebar/queries/issue_reference.query.graphql
'
;
import
issueReferenceQuery
from
'
~/sidebar/queries/issue_reference.query.graphql
'
;
import
issueSubscribedQuery
from
'
~/sidebar/queries/issue_subscribed.query.graphql
'
;
import
issueSubscribedQuery
from
'
~/sidebar/queries/issue_subscribed.query.graphql
'
;
import
issueTimeTrackingQuery
from
'
~/sidebar/queries/issue_time_tracking.query.graphql
'
;
import
issueTimeTrackingQuery
from
'
~/sidebar/queries/issue_time_tracking.query.graphql
'
;
import
mergeRequestMilestone
from
'
~/sidebar/queries/merge_request_milestone.query.graphql
'
;
import
mergeRequestReferenceQuery
from
'
~/sidebar/queries/merge_request_reference.query.graphql
'
;
import
mergeRequestReferenceQuery
from
'
~/sidebar/queries/merge_request_reference.query.graphql
'
;
import
mergeRequestSubscribed
from
'
~/sidebar/queries/merge_request_subscribed.query.graphql
'
;
import
mergeRequestSubscribed
from
'
~/sidebar/queries/merge_request_subscribed.query.graphql
'
;
import
mergeRequestTimeTrackingQuery
from
'
~/sidebar/queries/merge_request_time_tracking.query.graphql
'
;
import
mergeRequestTimeTrackingQuery
from
'
~/sidebar/queries/merge_request_time_tracking.query.graphql
'
;
...
@@ -24,6 +25,7 @@ import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscr
...
@@ -24,6 +25,7 @@ import updateEpicSubscriptionMutation from '~/sidebar/queries/update_epic_subscr
import
updateIssueConfidentialMutation
from
'
~/sidebar/queries/update_issue_confidential.mutation.graphql
'
;
import
updateIssueConfidentialMutation
from
'
~/sidebar/queries/update_issue_confidential.mutation.graphql
'
;
import
updateIssueDueDateMutation
from
'
~/sidebar/queries/update_issue_due_date.mutation.graphql
'
;
import
updateIssueDueDateMutation
from
'
~/sidebar/queries/update_issue_due_date.mutation.graphql
'
;
import
updateIssueSubscriptionMutation
from
'
~/sidebar/queries/update_issue_subscription.mutation.graphql
'
;
import
updateIssueSubscriptionMutation
from
'
~/sidebar/queries/update_issue_subscription.mutation.graphql
'
;
import
mergeRequestMilestoneMutation
from
'
~/sidebar/queries/update_merge_request_milestone.mutation.graphql
'
;
import
updateMergeRequestSubscriptionMutation
from
'
~/sidebar/queries/update_merge_request_subscription.mutation.graphql
'
;
import
updateMergeRequestSubscriptionMutation
from
'
~/sidebar/queries/update_merge_request_subscription.mutation.graphql
'
;
import
updateAlertAssigneesMutation
from
'
~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql
'
;
import
updateAlertAssigneesMutation
from
'
~/vue_shared/alert_details/graphql/mutations/alert_set_assignees.mutation.graphql
'
;
import
getAlertAssignees
from
'
~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql
'
;
import
getAlertAssignees
from
'
~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql
'
;
...
@@ -171,12 +173,19 @@ export const issuableMilestoneQueries = {
...
@@ -171,12 +173,19 @@ export const issuableMilestoneQueries = {
query
:
projectIssueMilestoneQuery
,
query
:
projectIssueMilestoneQuery
,
mutation
:
projectIssueMilestoneMutation
,
mutation
:
projectIssueMilestoneMutation
,
},
},
[
IssuableType
.
MergeRequest
]:
{
query
:
mergeRequestMilestone
,
mutation
:
mergeRequestMilestoneMutation
,
},
};
};
export
const
milestonesQueries
=
{
export
const
milestonesQueries
=
{
[
IssuableType
.
Issue
]:
{
[
IssuableType
.
Issue
]:
{
query
:
projectMilestonesQuery
,
query
:
projectMilestonesQuery
,
},
},
[
IssuableType
.
MergeRequest
]:
{
query
:
projectMilestonesQuery
,
},
};
};
export
const
IssuableAttributeType
=
{
export
const
IssuableAttributeType
=
{
...
...
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
dd5a87ca
...
@@ -18,6 +18,7 @@ import SidebarConfidentialityWidget from '~/sidebar/components/confidential/side
...
@@ -18,6 +18,7 @@ import SidebarConfidentialityWidget from '~/sidebar/components/confidential/side
import
SidebarDueDateWidget
from
'
~/sidebar/components/date/sidebar_date_widget.vue
'
;
import
SidebarDueDateWidget
from
'
~/sidebar/components/date/sidebar_date_widget.vue
'
;
import
SidebarParticipantsWidget
from
'
~/sidebar/components/participants/sidebar_participants_widget.vue
'
;
import
SidebarParticipantsWidget
from
'
~/sidebar/components/participants/sidebar_participants_widget.vue
'
;
import
SidebarReferenceWidget
from
'
~/sidebar/components/reference/sidebar_reference_widget.vue
'
;
import
SidebarReferenceWidget
from
'
~/sidebar/components/reference/sidebar_reference_widget.vue
'
;
import
SidebarDropdownWidget
from
'
~/sidebar/components/sidebar_dropdown_widget.vue
'
;
import
{
apolloProvider
}
from
'
~/sidebar/graphql
'
;
import
{
apolloProvider
}
from
'
~/sidebar/graphql
'
;
import
trackShowInviteMemberLink
from
'
~/sidebar/track_invite_members
'
;
import
trackShowInviteMemberLink
from
'
~/sidebar/track_invite_members
'
;
import
Translate
from
'
../vue_shared/translate
'
;
import
Translate
from
'
../vue_shared/translate
'
;
...
@@ -29,6 +30,7 @@ import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue';
...
@@ -29,6 +30,7 @@ import SidebarReviewers from './components/reviewers/sidebar_reviewers.vue';
import
SidebarSeverity
from
'
./components/severity/sidebar_severity.vue
'
;
import
SidebarSeverity
from
'
./components/severity/sidebar_severity.vue
'
;
import
SidebarSubscriptionsWidget
from
'
./components/subscriptions/sidebar_subscriptions_widget.vue
'
;
import
SidebarSubscriptionsWidget
from
'
./components/subscriptions/sidebar_subscriptions_widget.vue
'
;
import
SidebarTimeTracking
from
'
./components/time_tracking/sidebar_time_tracking.vue
'
;
import
SidebarTimeTracking
from
'
./components/time_tracking/sidebar_time_tracking.vue
'
;
import
{
IssuableAttributeType
}
from
'
./constants
'
;
import
SidebarMoveIssue
from
'
./lib/sidebar_move_issue
'
;
import
SidebarMoveIssue
from
'
./lib/sidebar_move_issue
'
;
Vue
.
use
(
Translate
);
Vue
.
use
(
Translate
);
...
@@ -154,7 +156,8 @@ function mountReviewersComponent(mediator) {
...
@@ -154,7 +156,8 @@ function mountReviewersComponent(mediator) {
issuableIid
:
String
(
iid
),
issuableIid
:
String
(
iid
),
projectPath
:
fullPath
,
projectPath
:
fullPath
,
field
:
el
.
dataset
.
field
,
field
:
el
.
dataset
.
field
,
issuableType
:
isInIssuePage
()
||
isInDesignPage
()
?
'
issue
'
:
'
merge_request
'
,
issuableType
:
isInIssuePage
()
||
isInDesignPage
()
?
IssuableType
.
Issue
:
IssuableType
.
MergeRequest
,
},
},
}),
}),
});
});
...
@@ -166,6 +169,40 @@ function mountReviewersComponent(mediator) {
...
@@ -166,6 +169,40 @@ function mountReviewersComponent(mediator) {
}
}
}
}
function
mountMilestoneSelect
()
{
const
el
=
document
.
querySelector
(
'
.js-milestone-select
'
);
if
(
!
el
)
{
return
false
;
}
const
{
canEdit
,
projectPath
,
issueIid
}
=
el
.
dataset
;
return
new
Vue
({
el
,
apolloProvider
,
components
:
{
SidebarDropdownWidget
,
},
provide
:
{
canUpdate
:
parseBoolean
(
canEdit
),
isClassicSidebar
:
true
,
},
render
:
(
createElement
)
=>
createElement
(
'
sidebar-dropdown-widget
'
,
{
props
:
{
attrWorkspacePath
:
projectPath
,
workspacePath
:
projectPath
,
iid
:
issueIid
,
issuableType
:
isInIssuePage
()
||
isInDesignPage
()
?
IssuableType
.
Issue
:
IssuableType
.
MergeRequest
,
issuableAttribute
:
IssuableAttributeType
.
Milestone
,
icon
:
'
clock
'
,
},
}),
});
}
export
function
mountSidebarLabels
()
{
export
function
mountSidebarLabels
()
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-labels
'
);
const
el
=
document
.
querySelector
(
'
.js-sidebar-labels
'
);
...
@@ -466,6 +503,7 @@ export function mountSidebar(mediator) {
...
@@ -466,6 +503,7 @@ export function mountSidebar(mediator) {
mountAssigneesComponentDeprecated
(
mediator
);
mountAssigneesComponentDeprecated
(
mediator
);
}
}
mountReviewersComponent
(
mediator
);
mountReviewersComponent
(
mediator
);
mountMilestoneSelect
();
mountConfidentialComponent
(
mediator
);
mountConfidentialComponent
(
mediator
);
mountDueDateComponent
(
mediator
);
mountDueDateComponent
(
mediator
);
mountReferenceComponent
(
mediator
);
mountReferenceComponent
(
mediator
);
...
...
app/assets/javascripts/sidebar/queries/merge_request_milestone.query.graphql
0 → 100644
View file @
dd5a87ca
#import "./milestone.fragment.graphql"
query
mergeRequestMilestone
(
$fullPath
:
ID
!,
$iid
:
String
!)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
__typename
issuable
:
mergeRequest
(
iid
:
$iid
)
{
__typename
id
attribute
:
milestone
{
...
MilestoneFragment
}
}
}
}
app/assets/javascripts/sidebar/queries/milestone.fragment.graphql
View file @
dd5a87ca
...
@@ -2,4 +2,5 @@ fragment MilestoneFragment on Milestone {
...
@@ -2,4 +2,5 @@ fragment MilestoneFragment on Milestone {
id
id
title
title
webUrl
:
webPath
webUrl
:
webPath
expired
}
}
app/assets/javascripts/sidebar/queries/project_issue_milestone.mutation.graphql
View file @
dd5a87ca
...
@@ -11,6 +11,7 @@ mutation projectIssueMilestoneMutation($fullPath: ID!, $iid: String!, $attribute
...
@@ -11,6 +11,7 @@ mutation projectIssueMilestoneMutation($fullPath: ID!, $iid: String!, $attribute
title
title
id
id
state
state
expired
}
}
}
}
}
}
...
...
app/assets/javascripts/sidebar/queries/project_milestones.query.graphql
View file @
dd5a87ca
...
@@ -3,7 +3,13 @@
...
@@ -3,7 +3,13 @@
query
projectMilestones
(
$fullPath
:
ID
!,
$title
:
String
,
$state
:
MilestoneStateEnum
)
{
query
projectMilestones
(
$fullPath
:
ID
!,
$title
:
String
,
$state
:
MilestoneStateEnum
)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
workspace
:
project
(
fullPath
:
$fullPath
)
{
__typename
__typename
attributes
:
milestones
(
searchTitle
:
$title
,
state
:
$state
)
{
attributes
:
milestones
(
searchTitle
:
$title
state
:
$state
sort
:
EXPIRED_LAST_DUE_DATE_ASC
first
:
20
includeAncestors
:
true
)
{
nodes
{
nodes
{
...
MilestoneFragment
...
MilestoneFragment
state
state
...
...
app/assets/javascripts/sidebar/queries/update_merge_request_milestone.mutation.graphql
0 → 100644
View file @
dd5a87ca
mutation
mergeRequestSetMilestone
(
$fullPath
:
ID
!,
$iid
:
String
!,
$attributeId
:
ID
)
{
issuableSetAttribute
:
mergeRequestSetMilestone
(
input
:
{
projectPath
:
$fullPath
,
iid
:
$iid
,
milestoneId
:
$attributeId
}
)
{
__typename
errors
issuable
:
mergeRequest
{
__typename
id
attribute
:
milestone
{
title
id
state
}
}
}
}
app/views/shared/issuable/_sidebar.html.haml
View file @
dd5a87ca
...
@@ -34,31 +34,8 @@
...
@@ -34,31 +34,8 @@
=
render_if_exists
'shared/issuable/sidebar_item_epic'
,
issuable_sidebar:
issuable_sidebar
,
group_path:
@project
.
group
.
full_path
,
project_path:
issuable_sidebar
[
:project_full_path
],
issue_iid:
issuable_sidebar
[
:iid
],
issuable_type:
issuable_type
=
render_if_exists
'shared/issuable/sidebar_item_epic'
,
issuable_sidebar:
issuable_sidebar
,
group_path:
@project
.
group
.
full_path
,
project_path:
issuable_sidebar
[
:project_full_path
],
issue_iid:
issuable_sidebar
[
:iid
],
issuable_type:
issuable_type
-
if
issuable_sidebar
[
:supports_milestone
]
-
if
issuable_sidebar
[
:supports_milestone
]
-
milestone
=
issuable_sidebar
[
:milestone
]
||
{}
.block.milestone
{
:class
=>
(
"gl-border-b-0!"
if
issuable_sidebar
[
:supports_iterations
]),
data:
{
qa_selector:
'milestone_block'
}
}
.block.milestone
{
:class
=>
(
"gl-border-b-0!"
if
issuable_sidebar
[
:supports_iterations
]),
data:
{
qa_selector:
'milestone_block'
}
}
.sidebar-collapsed-icon.has-tooltip
{
title:
sidebar_milestone_tooltip_label
(
milestone
),
data:
{
container:
'body'
,
html:
'true'
,
placement:
'left'
,
boundary:
'viewport'
}
}
.js-milestone-select
{
data:
{
can_edit:
can_edit_issuable
.
to_s
,
project_path:
issuable_sidebar
[
:project_full_path
],
issue_iid:
issuable_sidebar
[
:iid
]
}
}
=
sprite_icon
(
'clock'
)
%span
.milestone-title.collapse-truncated-title
-
if
milestone
.
present?
=
milestone
[
:title
]
-
else
=
_
(
'None'
)
.hide-collapsed.gl-line-height-20.gl-mb-2.gl-text-gray-900
{
data:
{
testid:
"milestone_title"
}
}
=
_
(
'Milestone'
)
=
loading_icon
(
css_class:
'gl-vertical-align-text-bottom hidden block-loading'
)
-
if
can_edit_issuable
=
link_to
_
(
'Edit'
),
'#'
,
class:
'js-sidebar-dropdown-toggle edit-link float-right'
,
data:
{
qa_selector:
"edit_milestone_link"
,
track_label:
"right_sidebar"
,
track_property:
"milestone"
,
track_event:
"click_edit_button"
,
track_value:
""
}
.value.hide-collapsed
-
if
milestone
.
present?
-
milestone_title
=
milestone
[
:expired
]
?
_
(
"%{milestone_name} (Past due)"
).
html_safe
%
{
milestone_name:
milestone
[
:title
]
}
:
milestone
[
:title
]
=
link_to
milestone_title
,
milestone
[
:web_url
],
class:
"bold has-tooltip"
,
title:
sidebar_milestone_remaining_days
(
milestone
),
data:
{
container:
"body"
,
html:
'true'
,
boundary:
'viewport'
,
qa_selector:
'milestone_link'
,
qa_title:
milestone
[
:title
]
}
-
else
%span
.no-value
=
_
(
'None'
)
.selectbox.hide-collapsed
=
f
.
hidden_field
'milestone_id'
,
value:
milestone
[
:id
],
id:
nil
=
dropdown_tag
(
'Milestone'
,
options:
{
title:
_
(
'Assign milestone'
),
toggle_class:
'js-milestone-select js-extra-options'
,
filter:
true
,
dropdown_class:
'dropdown-menu-selectable'
,
placeholder:
_
(
'Search milestones'
),
data:
{
show_no:
true
,
field_name:
"
#{
issuable_type
}
[milestone_id]"
,
project_id:
issuable_sidebar
[
:project_id
],
issuable_id:
issuable_sidebar
[
:id
],
ability_name:
issuable_type
,
issue_update:
issuable_sidebar
[
:issuable_json_path
],
use_id:
true
,
default_no:
true
,
selected:
milestone
[
:title
],
null_default:
true
,
display:
'static'
}})
-
if
@project
.
group
.
present?
&&
issuable_sidebar
[
:supports_iterations
]
-
if
@project
.
group
.
present?
&&
issuable_sidebar
[
:supports_iterations
]
.block
{
class:
'gl-pt-0!'
,
data:
{
qa_selector:
'iteration_container'
}
}
.block
{
class:
'gl-pt-0!'
,
data:
{
qa_selector:
'iteration_container'
}
}
...
...
locale/gitlab.pot
View file @
dd5a87ca
...
@@ -708,9 +708,6 @@ msgstr ""
...
@@ -708,9 +708,6 @@ msgstr ""
msgid "%{message} showing first %{warnings_displayed}"
msgid "%{message} showing first %{warnings_displayed}"
msgstr ""
msgstr ""
msgid "%{milestone_name} (Past due)"
msgstr ""
msgid "%{milestone} (expired)"
msgid "%{milestone} (expired)"
msgstr ""
msgstr ""
...
@@ -1097,6 +1094,9 @@ msgstr ""
...
@@ -1097,6 +1094,9 @@ msgstr ""
msgid "(deleted)"
msgid "(deleted)"
msgstr ""
msgstr ""
msgid "(expired)"
msgstr ""
msgid "(leave blank if you don't want to change it)"
msgid "(leave blank if you don't want to change it)"
msgstr ""
msgstr ""
...
...
qa/qa/page/component/issuable/sidebar.rb
View file @
dd5a87ca
...
@@ -40,16 +40,22 @@ module QA
...
@@ -40,16 +40,22 @@ module QA
base
.
view
'app/views/shared/issuable/_sidebar.html.haml'
do
base
.
view
'app/views/shared/issuable/_sidebar.html.haml'
do
element
:assignee_block
element
:assignee_block
element
:edit_milestone_link
element
:milestone_block
element
:milestone_block
element
:milestone_link
end
base
.
view
'app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue'
do
element
:milestone_link
,
'data-qa-selector="`${issuableAttribute}_link`"'
# rubocop:disable QA/ElementWithPattern
end
base
.
view
'app/assets/javascripts/sidebar/components/sidebar_editable_item.vue'
do
element
:edit_link
end
end
end
end
def
assign_milestone
(
milestone
)
def
assign_milestone
(
milestone
)
click_element
(
:edit_milestone_link
)
within_element
(
:milestone_block
)
do
within_element
(
:milestone_block
)
do
click_link
(
"
#{
milestone
.
title
}
"
)
click_element
(
:edit_link
)
click_on
(
milestone
.
title
)
end
end
wait_until
(
reload:
false
)
do
wait_until
(
reload:
false
)
do
...
@@ -89,7 +95,7 @@ module QA
...
@@ -89,7 +95,7 @@ module QA
def
has_milestone?
(
milestone_title
)
def
has_milestone?
(
milestone_title
)
wait_milestone_block_finish_loading
do
wait_milestone_block_finish_loading
do
has_element?
(
:milestone_link
,
t
itle
:
milestone_title
)
has_element?
(
:milestone_link
,
t
ext
:
milestone_title
)
end
end
end
end
...
...
spec/features/issues/issue_sidebar_spec.rb
View file @
dd5a87ca
...
@@ -259,37 +259,35 @@ RSpec.describe 'Issue Sidebar' do
...
@@ -259,37 +259,35 @@ RSpec.describe 'Issue Sidebar' do
end
end
context
'editing issue milestone'
,
:js
do
context
'editing issue milestone'
,
:js
do
let_it_be
(
:milestone_expired
)
{
create
(
:milestone
,
project:
project
,
due_date:
5
.
days
.
ago
)
}
let_it_be
(
:milestone_expired
)
{
create
(
:milestone
,
project:
project
,
title:
'Foo - expired'
,
due_date:
5
.
days
.
ago
)
}
let_it_be
(
:milestone_no_duedate
)
{
create
(
:milestone
,
project:
project
,
title:
'Foo - No due date'
)
}
let_it_be
(
:milestone_no_duedate
)
{
create
(
:milestone
,
project:
project
,
title:
'Foo - No due date'
)
}
let_it_be
(
:milestone1
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-1'
,
due_date:
20
.
days
.
from_now
)
}
let_it_be
(
:milestone1
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-1'
,
due_date:
20
.
days
.
from_now
)
}
let_it_be
(
:milestone2
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-2'
,
due_date:
15
.
days
.
from_now
)
}
let_it_be
(
:milestone2
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-2'
,
due_date:
15
.
days
.
from_now
)
}
let_it_be
(
:milestone3
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-3'
,
due_date:
10
.
days
.
from_now
)
}
let_it_be
(
:milestone3
)
{
create
(
:milestone
,
project:
project
,
title:
'Milestone-3'
,
due_date:
10
.
days
.
from_now
)
}
before
do
before
do
page
.
within
(
'
[data-testid="milestone_title"]
'
)
do
page
.
within
(
'
.block.milestone
'
)
do
click_on
'Edit'
click_
butt
on
'Edit'
end
end
wait_for_all_requests
end
end
it
'shows milestons list in the dropdown'
do
it
'shows mileston
e
s list in the dropdown'
do
page
.
within
(
'.block.milestone
.dropdown-content
'
)
do
page
.
within
(
'.block.milestone'
)
do
# 5 milestones + "No milestone" = 6 items
# 5 milestones + "No milestone" = 6 items
expect
(
page
.
find
(
'
ul'
)).
to
have_selector
(
'li[data-milestone-id]
'
,
count:
6
)
expect
(
page
.
find
(
'
.gl-new-dropdown-contents'
)).
to
have_selector
(
'li.gl-new-dropdown-item
'
,
count:
6
)
end
end
end
end
it
'shows expired milestone at the bottom of the list
'
do
it
'shows expired milestone at the bottom of the list
and milestone due earliest at the top of the list'
,
:aggregate_failures
do
page
.
within
(
'.block.milestone .
dropdown-content ul
'
)
do
page
.
within
(
'.block.milestone .
gl-new-dropdown-contents
'
)
do
expect
(
page
.
find
(
'li:last-child'
)).
to
have_content
milestone_expired
.
title
expect
(
page
.
find
(
'li:last-child'
)).
to
have_content
milestone_expired
.
title
end
end
it
'shows milestone due earliest at the top of the list'
do
expect
(
page
.
all
(
'li.gl-new-dropdown-item'
)[
1
]).
to
have_content
milestone3
.
title
page
.
within
(
'.block.milestone .dropdown-content ul'
)
do
expect
(
page
.
all
(
'li.gl-new-dropdown-item'
)[
2
]).
to
have_content
milestone2
.
title
expect
(
page
.
all
(
'li[data-milestone-id]'
)[
1
]).
to
have_content
milestone3
.
title
expect
(
page
.
all
(
'li.gl-new-dropdown-item'
)[
3
]).
to
have_content
milestone1
.
title
expect
(
page
.
all
(
'li[data-milestone-id]'
)[
2
]).
to
have_content
milestone2
.
title
expect
(
page
.
all
(
'li.gl-new-dropdown-item'
)[
4
]).
to
have_content
milestone_no_duedate
.
title
expect
(
page
.
all
(
'li[data-milestone-id]'
)[
3
]).
to
have_content
milestone1
.
title
expect
(
page
.
all
(
'li[data-milestone-id]'
)[
4
]).
to
have_content
milestone_no_duedate
.
title
end
end
end
end
end
end
...
...
spec/features/issues/user_edits_issue_spec.rb
View file @
dd5a87ca
...
@@ -333,37 +333,40 @@ RSpec.describe "Issues > User edits issue", :js do
...
@@ -333,37 +333,40 @@ RSpec.describe "Issues > User edits issue", :js do
describe
'update milestone'
do
describe
'update milestone'
do
context
'by authorized user'
do
context
'by authorized user'
do
it
'allows user to select
unassigned
'
do
it
'allows user to select
no milestone
'
do
visit
project_issue_path
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
page
.
within
(
'.milestone'
)
do
page
.
within
(
'.block.milestone'
)
do
expect
(
page
).
to
have_content
"None"
expect
(
page
).
to
have_content
'None'
end
click_button
'Edit'
wait_for_requests
click_button
'No milestone'
wait_for_requests
find
(
'.block.milestone .edit-link'
).
click
sleep
2
# wait for ajax stuff to complete
first
(
'.dropdown-content li'
).
click
sleep
2
page
.
within
(
'.milestone'
)
do
expect
(
page
).
to
have_content
'None'
expect
(
page
).
to
have_content
'None'
end
end
end
end
it
'allows user to de-select milestone'
do
it
'allows user to de-select milestone'
do
visit
project_issue_path
(
project
,
issue
)
visit
project_issue_path
(
project
,
issue
)
wait_for_requests
page
.
within
(
'.milestone'
)
do
page
.
within
(
'.milestone'
)
do
click_link
'Edit'
click_button
'Edit'
click_link
milestone
.
title
wait_for_requests
click_button
milestone
.
title
page
.
within
'
.value
'
do
page
.
within
'
[data-testid="select-milestone"]
'
do
expect
(
page
).
to
have_content
milestone
.
title
expect
(
page
).
to
have_content
milestone
.
title
end
end
click_link
'Edit'
click_button
'Edit'
click_link
milestone
.
title
wait_for_requests
click_button
'No milestone'
page
.
within
'
.value
'
do
page
.
within
'
[data-testid="select-milestone"]
'
do
expect
(
page
).
to
have_content
'None'
expect
(
page
).
to
have_content
'None'
end
end
end
end
...
@@ -371,16 +374,17 @@ RSpec.describe "Issues > User edits issue", :js do
...
@@ -371,16 +374,17 @@ RSpec.describe "Issues > User edits issue", :js do
it
'allows user to search milestone'
do
it
'allows user to search milestone'
do
visit
project_issue_path
(
project_with_milestones
,
issue_with_milestones
)
visit
project_issue_path
(
project_with_milestones
,
issue_with_milestones
)
wait_for_requests
page
.
within
(
'.milestone'
)
do
page
.
within
(
'.milestone'
)
do
click_
link
'Edit'
click_
button
'Edit'
wait_for_requests
wait_for_requests
# We need to enclose search string in quotes for exact match as all the milestone titles
# We need to enclose search string in quotes for exact match as all the milestone titles
# within tests are prefixed with `My title`.
# within tests are prefixed with `My title`.
find
(
'.
dropdown-input-field
'
,
visible:
true
).
send_keys
"
\"
#{
milestones
[
0
].
title
}
\"
"
find
(
'.
gl-form-input
'
,
visible:
true
).
send_keys
"
\"
#{
milestones
[
0
].
title
}
\"
"
wait_for_requests
wait_for_requests
page
.
within
'.
dropdown-content
'
do
page
.
within
'.
gl-new-dropdown-contents
'
do
expect
(
page
).
to
have_content
milestones
[
0
].
title
expect
(
page
).
to
have_content
milestones
[
0
].
title
end
end
end
end
...
...
spec/frontend/sidebar/mock_data.js
View file @
dd5a87ca
...
@@ -530,6 +530,7 @@ export const mockMilestone1 = {
...
@@ -530,6 +530,7 @@ export const mockMilestone1 = {
title
:
'
Foobar Milestone
'
,
title
:
'
Foobar Milestone
'
,
webUrl
:
'
http://gdk.test:3000/groups/gitlab-org/-/milestones/1
'
,
webUrl
:
'
http://gdk.test:3000/groups/gitlab-org/-/milestones/1
'
,
state
:
'
active
'
,
state
:
'
active
'
,
expired
:
false
,
};
};
export
const
mockMilestone2
=
{
export
const
mockMilestone2
=
{
...
@@ -538,6 +539,7 @@ export const mockMilestone2 = {
...
@@ -538,6 +539,7 @@ export const mockMilestone2 = {
title
:
'
Awesome Milestone
'
,
title
:
'
Awesome Milestone
'
,
webUrl
:
'
http://gdk.test:3000/groups/gitlab-org/-/milestones/2
'
,
webUrl
:
'
http://gdk.test:3000/groups/gitlab-org/-/milestones/2
'
,
state
:
'
active
'
,
state
:
'
active
'
,
expired
:
false
,
};
};
export
const
mockProjectMilestonesResponse
=
{
export
const
mockProjectMilestonesResponse
=
{
...
@@ -571,6 +573,7 @@ export const mockMilestoneMutationResponse = {
...
@@ -571,6 +573,7 @@ export const mockMilestoneMutationResponse = {
id
:
'
gid://gitlab/Milestone/2
'
,
id
:
'
gid://gitlab/Milestone/2
'
,
title
:
'
Awesome Milestone
'
,
title
:
'
Awesome Milestone
'
,
state
:
'
active
'
,
state
:
'
active
'
,
expired
:
false
,
__typename
:
'
Milestone
'
,
__typename
:
'
Milestone
'
,
},
},
__typename
:
'
Issue
'
,
__typename
:
'
Issue
'
,
...
...
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