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
e5d48566
Commit
e5d48566
authored
Dec 05, 2017
by
Eric Eastwood
Committed by
Phil Hughes
Dec 05, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor sidebar weight block to use Vue and be async + add to Issue Boards
parent
ded4e2aa
Changes
33
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
1060 additions
and
169 deletions
+1060
-169
app/assets/javascripts/boards/boards_bundle.js
app/assets/javascripts/boards/boards_bundle.js
+24
-0
app/assets/javascripts/boards/components/board_sidebar.js
app/assets/javascripts/boards/components/board_sidebar.js
+2
-0
app/assets/javascripts/boards/models/issue.js
app/assets/javascripts/boards/models/issue.js
+8
-0
app/assets/javascripts/boards/services/board_service.js
app/assets/javascripts/boards/services/board_service.js
+8
-0
app/assets/javascripts/gl_dropdown.js
app/assets/javascripts/gl_dropdown.js
+2
-1
app/assets/javascripts/right_sidebar.js
app/assets/javascripts/right_sidebar.js
+3
-3
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+104
-0
app/assets/javascripts/sidebar/sidebar_bundle.js
app/assets/javascripts/sidebar/sidebar_bundle.js
+5
-101
app/assets/javascripts/sidebar/sidebar_mediator.js
app/assets/javascripts/sidebar/sidebar_mediator.js
+22
-14
app/assets/javascripts/sidebar/stores/sidebar_store.js
app/assets/javascripts/sidebar/stores/sidebar_store.js
+5
-0
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+3
-1
app/serializers/issuable_sidebar_entity.rb
app/serializers/issuable_sidebar_entity.rb
+1
-0
app/views/shared/boards/components/_sidebar.html.haml
app/views/shared/boards/components/_sidebar.html.haml
+1
-0
app/views/shared/issuable/_sidebar.html.haml
app/views/shared/issuable/_sidebar.html.haml
+2
-25
changelogs/unreleased-ee/1379-update-sidebar-weight-to-vue-and-add-to-boards.yml
...e/1379-update-sidebar-weight-to-vue-and-add-to-boards.yml
+5
-0
ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight.vue
.../javascripts/sidebar/components/weight/sidebar_weight.vue
+48
-0
ee/app/assets/javascripts/sidebar/components/weight/weight.vue
...p/assets/javascripts/sidebar/components/weight/weight.vue
+243
-0
ee/app/assets/javascripts/sidebar/mount_sidebar.js
ee/app/assets/javascripts/sidebar/mount_sidebar.js
+27
-0
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
+28
-0
ee/app/assets/javascripts/sidebar/stores/sidebar_store.js
ee/app/assets/javascripts/sidebar/stores/sidebar_store.js
+22
-0
ee/app/serializers/ee/issuable_sidebar_entity.rb
ee/app/serializers/ee/issuable_sidebar_entity.rb
+9
-0
ee/app/views/shared/boards/components/sidebar/_weight.html.haml
.../views/shared/boards/components/sidebar/_weight.html.haml
+7
-0
spec/ee/spec/features/boards/sidebar_spec.rb
spec/ee/spec/features/boards/sidebar_spec.rb
+73
-9
spec/fixtures/api/schemas/entities/issue_sidebar.json
spec/fixtures/api/schemas/entities/issue_sidebar.json
+2
-1
spec/javascripts/boards/issue_spec.js
spec/javascripts/boards/issue_spec.js
+6
-0
spec/javascripts/sidebar/ee_mock_data.js
spec/javascripts/sidebar/ee_mock_data.js
+74
-0
spec/javascripts/sidebar/ee_sidebar_mediator_spec.js
spec/javascripts/sidebar/ee_sidebar_mediator_spec.js
+26
-0
spec/javascripts/sidebar/ee_sidebar_store_spec.js
spec/javascripts/sidebar/ee_sidebar_store_spec.js
+33
-0
spec/javascripts/sidebar/mock_data.js
spec/javascripts/sidebar/mock_data.js
+72
-10
spec/javascripts/sidebar/sidebar_mediator_spec.js
spec/javascripts/sidebar/sidebar_mediator_spec.js
+23
-4
spec/javascripts/sidebar/sidebar_store_spec.js
spec/javascripts/sidebar/sidebar_store_spec.js
+6
-0
spec/javascripts/sidebar/sidebar_weight_spec.js
spec/javascripts/sidebar/sidebar_weight_spec.js
+38
-0
spec/javascripts/sidebar/weight_spec.js
spec/javascripts/sidebar/weight_spec.js
+128
-0
No files found.
app/assets/javascripts/boards/boards_bundle.js
View file @
e5d48566
...
...
@@ -89,12 +89,14 @@ $(() => {
eventHub
.
$on
(
'
newDetailIssue
'
,
this
.
updateDetailIssue
);
eventHub
.
$on
(
'
clearDetailIssue
'
,
this
.
clearDetailIssue
);
sidebarEventHub
.
$on
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
sidebarEventHub
.
$on
(
'
updateWeight
'
,
this
.
updateWeight
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
updateTokens
'
,
this
.
updateTokens
);
eventHub
.
$off
(
'
newDetailIssue
'
,
this
.
updateDetailIssue
);
eventHub
.
$off
(
'
clearDetailIssue
'
,
this
.
clearDetailIssue
);
sidebarEventHub
.
$off
(
'
toggleSubscription
'
,
this
.
toggleSubscription
);
sidebarEventHub
.
$off
(
'
updateWeight
'
,
this
.
updateWeight
);
},
mounted
()
{
this
.
filterManager
=
new
FilteredSearchBoards
(
Store
.
filter
,
true
,
Store
.
cantEdit
);
...
...
@@ -131,16 +133,20 @@ $(() => {
const
sidebarInfoEndpoint
=
newIssue
.
sidebarInfoEndpoint
;
if
(
sidebarInfoEndpoint
&&
newIssue
.
subscribed
===
undefined
)
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
true
);
newIssue
.
setFetchingState
(
'
weight
'
,
true
);
BoardService
.
getIssueInfo
(
sidebarInfoEndpoint
)
.
then
(
res
=>
res
.
json
())
.
then
((
data
)
=>
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
newIssue
.
setFetchingState
(
'
weight
'
,
false
);
newIssue
.
updateData
({
subscribed
:
data
.
subscribed
,
weight
:
data
.
weight
,
});
})
.
catch
(()
=>
{
newIssue
.
setFetchingState
(
'
subscriptions
'
,
false
);
newIssue
.
setFetchingState
(
'
weight
'
,
false
);
Flash
(
__
(
'
An error occurred while fetching sidebar data
'
));
});
}
...
...
@@ -166,6 +172,24 @@ $(() => {
Flash
(
__
(
'
An error occurred when toggling the notification subscription
'
));
});
}
},
updateWeight
(
newWeight
,
id
)
{
const
issue
=
Store
.
detail
.
issue
;
if
(
issue
.
id
===
id
&&
issue
.
sidebarInfoEndpoint
)
{
issue
.
setLoadingState
(
'
weight
'
,
true
);
BoardService
.
updateWeight
(
issue
.
sidebarInfoEndpoint
,
newWeight
)
.
then
(
res
=>
res
.
json
())
.
then
((
data
)
=>
{
issue
.
setLoadingState
(
'
weight
'
,
false
);
issue
.
updateData
({
weight
:
data
.
weight
,
});
})
.
catch
(()
=>
{
issue
.
setLoadingState
(
'
weight
'
,
false
);
Flash
(
__
(
'
An error occurred when updating the issue weight
'
));
});
}
}
},
});
...
...
app/assets/javascripts/boards/components/board_sidebar.js
View file @
e5d48566
...
...
@@ -3,6 +3,7 @@
/* global Sidebar */
import
Vue
from
'
vue
'
;
import
weight
from
'
ee/sidebar/components/weight/weight.vue
'
;
import
Flash
from
'
../../flash
'
;
import
eventHub
from
'
../../sidebar/event_hub
'
;
import
assigneeTitle
from
'
../../sidebar/components/assignees/assignee_title
'
;
...
...
@@ -124,5 +125,6 @@ gl.issueBoards.BoardSidebar = Vue.extend({
assignees
,
removeBtn
:
gl
.
issueBoards
.
RemoveIssueBtn
,
subscriptions
,
weight
,
},
});
app/assets/javascripts/boards/models/issue.js
View file @
e5d48566
...
...
@@ -20,6 +20,10 @@ class ListIssue {
this
.
position
=
obj
.
relative_position
||
Infinity
;
this
.
isFetching
=
{
subscriptions
:
true
,
weight
:
true
,
};
this
.
isLoading
=
{
weight
:
false
,
};
this
.
sidebarInfoEndpoint
=
obj
.
issue_sidebar_endpoint
;
this
.
toggleSubscriptionEndpoint
=
obj
.
toggle_subscription_endpoint
;
...
...
@@ -94,6 +98,10 @@ class ListIssue {
this
.
isFetching
[
key
]
=
value
;
}
setLoadingState
(
key
,
value
)
{
this
.
isLoading
[
key
]
=
value
;
}
update
(
url
)
{
const
data
=
{
issue
:
{
...
...
app/assets/javascripts/boards/services/board_service.js
View file @
e5d48566
...
...
@@ -122,6 +122,14 @@ export default class BoardService {
return
Vue
.
http
.
get
(
endpoint
);
}
static
updateWeight
(
endpoint
,
weight
=
null
)
{
return
Vue
.
http
.
put
(
endpoint
,
{
'
issue[weight]
'
:
weight
,
},
{
emulateJSON
:
true
,
});
}
static
toggleIssueSubscription
(
endpoint
)
{
return
Vue
.
http
.
post
(
endpoint
);
}
...
...
app/assets/javascripts/gl_dropdown.js
View file @
e5d48566
...
...
@@ -514,10 +514,11 @@ GitLabDropdown = (function() {
const
dropdownToggle
=
this
.
dropdown
.
find
(
'
.dropdown-menu-toggle
'
);
const
hasFilterBulkUpdate
=
dropdownToggle
.
hasClass
(
'
js-filter-bulk-update
'
);
const
shouldRefreshOnOpen
=
dropdownToggle
.
hasClass
(
'
js-gl-dropdown-refresh-on-open
'
);
const
hasMultiSelect
=
dropdownToggle
.
hasClass
(
'
js-multiselect
'
);
// Makes indeterminate items effective
if
(
this
.
fullData
&&
hasFilterBulkUpdate
)
{
if
(
this
.
fullData
&&
(
shouldRefreshOnOpen
||
hasFilterBulkUpdate
)
)
{
this
.
parseData
(
this
.
fullData
);
}
...
...
app/assets/javascripts/right_sidebar.js
View file @
e5d48566
...
...
@@ -15,7 +15,7 @@ import Cookies from 'js-cookie';
Sidebar
.
prototype
.
removeListeners
=
function
()
{
this
.
sidebar
.
off
(
'
click
'
,
'
.sidebar-collapsed-icon
'
);
$
(
'
.dropdown
'
)
.
off
(
'
hidden.gl.dropdown
'
);
this
.
sidebar
.
off
(
'
hidden.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loading.gl.dropdown
'
);
$
(
'
.dropdown
'
).
off
(
'
loaded.gl.dropdown
'
);
$
(
document
).
off
(
'
click
'
,
'
.js-sidebar-toggle
'
);
...
...
@@ -25,7 +25,7 @@ import Cookies from 'js-cookie';
const
$document
=
$
(
document
);
this
.
sidebar
.
on
(
'
click
'
,
'
.sidebar-collapsed-icon
'
,
this
,
this
.
sidebarCollapseClicked
);
$
(
'
.dropdown
'
)
.
on
(
'
hidden.gl.dropdown
'
,
this
,
this
.
onSidebarDropdownHidden
);
this
.
sidebar
.
on
(
'
hidden.gl.dropdown
'
,
this
,
this
.
onSidebarDropdownHidden
);
$
(
'
.dropdown
'
).
on
(
'
loading.gl.dropdown
'
,
this
.
sidebarDropdownLoading
);
$
(
'
.dropdown
'
).
on
(
'
loaded.gl.dropdown
'
,
this
.
sidebarDropdownLoaded
);
...
...
@@ -180,7 +180,7 @@ import Cookies from 'js-cookie';
var
$block
,
sidebar
;
sidebar
=
e
.
data
;
e
.
preventDefault
();
$block
=
$
(
this
).
closest
(
'
.block
'
);
$block
=
$
(
e
.
target
).
closest
(
'
.block
'
);
return
sidebar
.
sidebarDropdownHidden
(
$block
);
};
...
...
app/assets/javascripts/sidebar/mount_sidebar.js
0 → 100644
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
SidebarTimeTracking
from
'
./components/time_tracking/sidebar_time_tracking
'
;
import
SidebarAssignees
from
'
./components/assignees/sidebar_assignees
'
;
import
ConfidentialIssueSidebar
from
'
./components/confidential/confidential_issue_sidebar.vue
'
;
import
SidebarMoveIssue
from
'
./lib/sidebar_move_issue
'
;
import
LockIssueSidebar
from
'
./components/lock/lock_issue_sidebar.vue
'
;
import
sidebarParticipants
from
'
./components/participants/sidebar_participants.vue
'
;
import
sidebarSubscriptions
from
'
./components/subscriptions/sidebar_subscriptions.vue
'
;
import
Translate
from
'
../vue_shared/translate
'
;
Vue
.
use
(
Translate
);
function
mountConfidentialComponent
(
mediator
)
{
const
el
=
document
.
getElementById
(
'
js-confidential-entry-point
'
);
if
(
!
el
)
return
;
const
dataNode
=
document
.
getElementById
(
'
js-confidential-issue-data
'
);
const
initialData
=
JSON
.
parse
(
dataNode
.
innerHTML
);
const
ConfidentialComp
=
Vue
.
extend
(
ConfidentialIssueSidebar
);
new
ConfidentialComp
({
propsData
:
{
isConfidential
:
initialData
.
is_confidential
,
isEditable
:
initialData
.
is_editable
,
service
:
mediator
.
service
,
},
}).
$mount
(
el
);
}
function
mountLockComponent
(
mediator
)
{
const
el
=
document
.
getElementById
(
'
js-lock-entry-point
'
);
if
(
!
el
)
return
;
const
dataNode
=
document
.
getElementById
(
'
js-lock-issue-data
'
);
const
initialData
=
JSON
.
parse
(
dataNode
.
innerHTML
);
const
LockComp
=
Vue
.
extend
(
LockIssueSidebar
);
new
LockComp
({
propsData
:
{
isLocked
:
initialData
.
is_locked
,
isEditable
:
initialData
.
is_editable
,
mediator
,
issuableType
:
gl
.
utils
.
isInIssuePage
()
?
'
issue
'
:
'
merge_request
'
,
},
}).
$mount
(
el
);
}
function
mountParticipantsComponent
()
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-participants-entry-point
'
);
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
sidebarParticipants
,
},
render
:
createElement
=>
createElement
(
'
sidebar-participants
'
,
{}),
});
}
function
mountSubscriptionsComponent
()
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-subscriptions-entry-point
'
);
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
sidebarSubscriptions
,
},
render
:
createElement
=>
createElement
(
'
sidebar-subscriptions
'
,
{}),
});
}
function
mount
(
mediator
)
{
const
sidebarAssigneesEl
=
document
.
getElementById
(
'
js-vue-sidebar-assignees
'
);
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if
(
sidebarAssigneesEl
)
{
new
Vue
(
SidebarAssignees
).
$mount
(
sidebarAssigneesEl
);
}
mountConfidentialComponent
(
mediator
);
mountLockComponent
(
mediator
);
mountParticipantsComponent
();
mountSubscriptionsComponent
();
new
SidebarMoveIssue
(
mediator
,
$
(
'
.js-move-issue
'
),
$
(
'
.js-move-issue-confirmation-button
'
),
).
init
();
new
Vue
(
SidebarTimeTracking
).
$mount
(
'
#issuable-time-tracker
'
);
}
export
default
mount
;
app/assets/javascripts/sidebar/sidebar_bundle.js
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
SidebarTimeTracking
from
'
./components/time_tracking/sidebar_time_tracking
'
;
import
SidebarAssignees
from
'
./components/assignees/sidebar_assignees
'
;
import
ConfidentialIssueSidebar
from
'
./components/confidential/confidential_issue_sidebar.vue
'
;
import
SidebarMoveIssue
from
'
./lib/sidebar_move_issue
'
;
import
LockIssueSidebar
from
'
./components/lock/lock_issue_sidebar.vue
'
;
import
sidebarParticipants
from
'
./components/participants/sidebar_participants.vue
'
;
import
sidebarSubscriptions
from
'
./components/subscriptions/sidebar_subscriptions.vue
'
;
import
Translate
from
'
../vue_shared/translate
'
;
import
Mediator
from
'
./sidebar_mediator
'
;
Vue
.
use
(
Translate
);
function
mountConfidentialComponent
(
mediator
)
{
const
el
=
document
.
getElementById
(
'
js-confidential-entry-point
'
);
if
(
!
el
)
return
;
const
dataNode
=
document
.
getElementById
(
'
js-confidential-issue-data
'
);
const
initialData
=
JSON
.
parse
(
dataNode
.
innerHTML
);
const
ConfidentialComp
=
Vue
.
extend
(
ConfidentialIssueSidebar
);
new
ConfidentialComp
({
propsData
:
{
isConfidential
:
initialData
.
is_confidential
,
isEditable
:
initialData
.
is_editable
,
service
:
mediator
.
service
,
},
}).
$mount
(
el
);
}
function
mountLockComponent
(
mediator
)
{
const
el
=
document
.
getElementById
(
'
js-lock-entry-point
'
);
if
(
!
el
)
return
;
const
dataNode
=
document
.
getElementById
(
'
js-lock-issue-data
'
);
const
initialData
=
JSON
.
parse
(
dataNode
.
innerHTML
);
const
LockComp
=
Vue
.
extend
(
LockIssueSidebar
);
new
LockComp
({
propsData
:
{
isLocked
:
initialData
.
is_locked
,
isEditable
:
initialData
.
is_editable
,
mediator
,
issuableType
:
gl
.
utils
.
isInIssuePage
()
?
'
issue
'
:
'
merge_request
'
,
},
}).
$mount
(
el
);
}
function
mountParticipantsComponent
()
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-participants-entry-point
'
);
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
sidebarParticipants
,
},
render
:
createElement
=>
createElement
(
'
sidebar-participants
'
,
{}),
});
}
function
mountSubscriptionsComponent
()
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-subscriptions-entry-point
'
);
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
sidebarSubscriptions
,
},
render
:
createElement
=>
createElement
(
'
sidebar-subscriptions
'
,
{}),
});
}
import
mountSidebarEE
from
'
ee/sidebar/mount_sidebar
'
;
import
Mediator
from
'
ee/sidebar/sidebar_mediator
'
;
import
mountSidebar
from
'
./mount_sidebar
'
;
function
domContentLoaded
()
{
const
sidebarOptions
=
JSON
.
parse
(
document
.
querySelector
(
'
.js-sidebar-options
'
).
innerHTML
);
const
mediator
=
new
Mediator
(
sidebarOptions
);
mediator
.
fetch
();
const
sidebarAssigneesEl
=
document
.
getElementById
(
'
js-vue-sidebar-assignees
'
);
// Only create the sidebarAssignees vue app if it is found in the DOM
// We currently do not use sidebarAssignees for the MR page
if
(
sidebarAssigneesEl
)
{
new
Vue
(
SidebarAssignees
).
$mount
(
sidebarAssigneesEl
);
}
mountConfidentialComponent
(
mediator
);
mountLockComponent
(
mediator
);
mountParticipantsComponent
();
mountSubscriptionsComponent
();
new
SidebarMoveIssue
(
mediator
,
$
(
'
.js-move-issue
'
),
$
(
'
.js-move-issue-confirmation-button
'
),
).
init
();
new
Vue
(
SidebarTimeTracking
).
$mount
(
'
#issuable-time-tracker
'
);
mountSidebar
(
mediator
);
mountSidebarEE
(
mediator
);
}
document
.
addEventListener
(
'
DOMContentLoaded
'
,
domContentLoaded
);
...
...
app/assets/javascripts/sidebar/sidebar_mediator.js
View file @
e5d48566
import
Store
from
'
ee/sidebar/stores/sidebar_store
'
;
import
Flash
from
'
../flash
'
;
import
Service
from
'
./services/sidebar_service
'
;
import
Store
from
'
./stores/sidebar_store
'
;
export
default
class
SidebarMediator
{
constructor
(
options
)
{
if
(
!
SidebarMediator
.
singleton
)
{
this
.
store
=
new
Store
(
options
);
this
.
service
=
new
Service
({
endpoint
:
options
.
endpoint
,
toggleSubscriptionEndpoint
:
options
.
toggleSubscriptionEndpoint
,
moveIssueEndpoint
:
options
.
moveIssueEndpoint
,
projectsAutocompleteEndpoint
:
options
.
projectsAutocompleteEndpoint
,
});
SidebarMediator
.
singleton
=
this
;
this
.
initSingleton
(
options
);
}
return
SidebarMediator
.
singleton
;
}
initSingleton
(
options
)
{
this
.
store
=
new
Store
(
options
);
this
.
service
=
new
Service
({
endpoint
:
options
.
endpoint
,
toggleSubscriptionEndpoint
:
options
.
toggleSubscriptionEndpoint
,
moveIssueEndpoint
:
options
.
moveIssueEndpoint
,
projectsAutocompleteEndpoint
:
options
.
projectsAutocompleteEndpoint
,
});
SidebarMediator
.
singleton
=
this
;
}
assignYourself
()
{
this
.
store
.
addAssignee
(
this
.
store
.
currentUser
);
}
...
...
@@ -35,17 +39,21 @@ export default class SidebarMediator {
}
fetch
()
{
this
.
service
.
get
()
return
this
.
service
.
get
()
.
then
(
response
=>
response
.
json
())
.
then
((
data
)
=>
{
this
.
store
.
setAssigneeData
(
data
);
this
.
store
.
setTimeTrackingData
(
data
);
this
.
store
.
setParticipantsData
(
data
);
this
.
store
.
setSubscriptionsData
(
data
);
this
.
processFetchedData
(
data
);
})
.
catch
(()
=>
new
Flash
(
'
Error occurred when fetching sidebar data
'
));
}
processFetchedData
(
data
)
{
this
.
store
.
setAssigneeData
(
data
);
this
.
store
.
setTimeTrackingData
(
data
);
this
.
store
.
setParticipantsData
(
data
);
this
.
store
.
setSubscriptionsData
(
data
);
}
toggleSubscription
()
{
this
.
store
.
setFetchingState
(
'
subscriptions
'
,
true
);
return
this
.
service
.
toggleSubscription
()
...
...
app/assets/javascripts/sidebar/stores/sidebar_store.js
View file @
e5d48566
...
...
@@ -15,6 +15,7 @@ export default class SidebarStore {
participants
:
true
,
subscriptions
:
true
,
};
this
.
isLoading
=
{};
this
.
autocompleteProjects
=
[];
this
.
moveToProjectId
=
0
;
this
.
isLockDialogOpen
=
false
;
...
...
@@ -55,6 +56,10 @@ export default class SidebarStore {
this
.
isFetching
[
key
]
=
value
;
}
setLoadingState
(
key
,
value
)
{
this
.
isLoading
[
key
]
=
value
;
}
addAssignee
(
assignee
)
{
if
(
!
this
.
findAssignee
(
assignee
))
{
this
.
assignees
.
push
(
assignee
);
...
...
app/helpers/issuables_helper.rb
View file @
e5d48566
...
...
@@ -367,7 +367,9 @@ module IssuablesHelper
editable:
can_edit_issuable
,
currentUser:
current_user
.
as_json
(
only:
[
:username
,
:id
,
:name
],
methods: :avatar_url
),
rootPath:
root_path
,
fullPath:
@project
.
full_path
fullPath:
@project
.
full_path
,
weightOptions:
Issue
.
weight_options
,
weightNoneValue:
Issue
::
WEIGHT_NONE
}
end
...
...
app/serializers/issuable_sidebar_entity.rb
View file @
e5d48566
class
IssuableSidebarEntity
<
Grape
::
Entity
include
RequestAwareEntity
prepend
::
EE
::
IssuableSidebarEntity
expose
:participants
,
using:
::
API
::
Entities
::
UserBasic
do
|
issuable
|
issuable
.
participants
(
request
.
current_user
)
...
...
app/views/shared/boards/components/_sidebar.html.haml
View file @
e5d48566
...
...
@@ -23,6 +23,7 @@
=
render
"shared/boards/components/sidebar/milestone"
=
render
"shared/boards/components/sidebar/due_date"
=
render
"shared/boards/components/sidebar/labels"
=
render
"shared/boards/components/sidebar/weight"
=
render
"shared/boards/components/sidebar/notifications"
%remove-btn
{
":issue"
=>
"issue"
,
":issue-update"
=>
"'#{build_issue_link_base}/' + issue.iid + '.json'"
,
...
...
app/views/shared/issuable/_sidebar.html.haml
View file @
e5d48566
...
...
@@ -116,31 +116,8 @@
=
render
partial:
"shared/issuable/label_page_create"
-
if
issuable
.
supports_weight?
.block.weight
.sidebar-collapsed-icon
=
icon
(
'balance-scale'
)
%span
-
if
issuable
.
weight
=
issuable
.
weight
-
else
No
.title.hide-collapsed
Weight
=
icon
(
'spinner spin'
,
class:
'block-loading'
)
-
if
can?
(
current_user
,
:"admin_
#{
issuable
.
to_ability_name
}
"
,
@project
)
=
link_to
'Edit'
,
'#'
,
class:
'edit-link pull-right'
.value.hide-collapsed
-
if
issuable
.
weight
%strong
=
issuable
.
weight
-
else
%span
.no-value
None
.selectbox.hide-collapsed
=
weight_dropdown_tag
(
issuable
,
title:
'Change weight'
,
data:
{
field_name:
'weight'
,
issue_update:
"
#{
issuable_json_path
(
issuable
)
}
"
,
ability_name:
"
#{
issuable
.
to_ability_name
}
"
})
do
%ul
-
Issue
.
weight_options
.
each
do
|
weight
|
%li
%a
{
href:
"#"
,
data:
{
id:
weight
,
none:
weight
==
Issue
::
WEIGHT_NONE
},
class:
(
"is-active"
if
params
[
:weight
]
==
weight
.
to_s
)
}
=
weight
.js-sidebar-weight-entry-point
=
render
'shared/promotions/promote_issue_weights'
-
if
issuable
.
has_attribute?
(
:confidential
)
...
...
changelogs/unreleased-ee/1379-update-sidebar-weight-to-vue-and-add-to-boards.yml
0 → 100644
View file @
e5d48566
---
title
:
View, add, and edit weight on Issue from the Issue Board contextual sidebar
merge_request
:
3566
author
:
type
:
added
ee/app/assets/javascripts/sidebar/components/weight/sidebar_weight.vue
0 → 100644
View file @
e5d48566
<
script
>
import
Flash
from
'
~/flash
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
weightComponent
from
'
./weight.vue
'
;
export
default
{
props
:
{
mediator
:
{
required
:
true
,
type
:
Object
,
validator
(
mediatorObject
)
{
return
mediatorObject
.
updateWeight
&&
mediatorObject
.
store
;
},
},
},
components
:
{
weight
:
weightComponent
,
},
methods
:
{
onUpdateWeight
(
newWeight
)
{
this
.
mediator
.
updateWeight
(
newWeight
)
.
catch
(()
=>
{
Flash
(
'
Error occurred while updating the issue weight
'
);
});
},
},
created
()
{
eventHub
.
$on
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
beforeDestroy
()
{
eventHub
.
$off
(
'
updateWeight
'
,
this
.
onUpdateWeight
);
},
};
</
script
>
<
template
>
<weight
:fetching=
"mediator.store.isFetching.weight"
:loading=
"mediator.store.isLoading.weight"
:weight=
"mediator.store.weight"
:weight-options=
"mediator.store.weightOptions"
:weight-none-value=
"mediator.store.weightNoneValue"
:editable=
"mediator.store.editable"
/>
</
template
>
ee/app/assets/javascripts/sidebar/components/weight/weight.vue
0 → 100644
View file @
e5d48566
<
script
>
import
$
from
'
jquery
'
;
import
{
s__
}
from
'
~/locale
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
icon
from
'
~/vue_shared/components/icon.vue
'
;
import
loadingIcon
from
'
~/vue_shared/components/loading_icon.vue
'
;
export
default
{
props
:
{
fetching
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
loading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
weight
:
{
type
:
Number
,
required
:
false
,
},
weightOptions
:
{
type
:
Array
,
required
:
true
,
},
weightNoneValue
:
{
type
:
String
,
required
:
true
,
},
editable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
id
:
{
type
:
Number
,
required
:
false
,
},
},
data
()
{
return
{
shouldShowDropdown
:
false
,
collapseAfterDropdownCloses
:
false
,
};
},
components
:
{
icon
,
loadingIcon
,
},
computed
:
{
isNoValue
()
{
return
this
.
checkIfNoValue
(
this
.
weight
);
},
collapsedWeightLabel
()
{
let
label
=
this
.
weight
;
if
(
this
.
checkIfNoValue
(
this
.
weight
))
{
label
=
s__
(
'
Sidebar|No
'
);
}
return
label
;
},
noValueLabel
()
{
return
s__
(
'
Sidebar|None
'
);
},
changeWeightLabel
()
{
return
s__
(
'
Sidebar|Change weight
'
);
},
dropdownToggleLabel
()
{
let
label
=
this
.
weight
;
if
(
this
.
checkIfNoValue
(
this
.
weight
))
{
label
=
s__
(
'
Sidebar|Weight
'
);
}
return
label
;
},
shouldShowWeight
()
{
return
!
this
.
fetching
&&
!
this
.
shouldShowDropdown
;
},
},
methods
:
{
checkIfNoValue
(
weight
)
{
return
weight
===
undefined
||
weight
===
null
||
weight
===
0
||
weight
===
this
.
weightNoneValue
;
},
showDropdown
()
{
this
.
shouldShowDropdown
=
true
;
// Trigger the bootstrap dropdown
setTimeout
(()
=>
{
$
(
this
.
$refs
.
dropdownToggle
).
dropdown
(
'
toggle
'
);
});
},
onCollapsedClick
()
{
this
.
collapseAfterDropdownCloses
=
true
;
this
.
showDropdown
();
},
},
mounted
()
{
$
(
this
.
$refs
.
weightDropdown
).
glDropdown
({
showMenuAbove
:
false
,
selectable
:
true
,
filterable
:
false
,
multiSelect
:
false
,
data
:
(
searchTerm
,
callback
)
=>
{
callback
(
this
.
weightOptions
);
},
renderRow
:
(
weight
)
=>
{
const
isActive
=
weight
===
this
.
weight
||
(
this
.
checkIfNoValue
(
weight
)
&&
this
.
checkIfNoValue
(
this
.
weight
));
return
`
<li>
<a href="#" class="
${
isActive
?
'
is-active
'
:
''
}
">
${
weight
}
</a>
</li>
`
;
},
hidden
:
()
=>
{
this
.
shouldShowDropdown
=
false
;
this
.
collapseAfterDropdownCloses
=
false
;
},
clicked
:
(
options
)
=>
{
const
selectedValue
=
this
.
checkIfNoValue
(
options
.
selectedObj
)
?
null
:
options
.
selectedObj
;
const
resultantValue
=
options
.
isMarking
?
selectedValue
:
null
;
eventHub
.
$emit
(
'
updateWeight
'
,
resultantValue
,
this
.
id
);
},
});
},
};
</
script
>
<
template
>
<div
class=
"block weight"
:class=
"
{ 'collapse-after-update': collapseAfterDropdownCloses }"
>
<div
class=
"sidebar-collapsed-icon js-weight-collapsed-block"
@
click=
"onCollapsedClick"
>
<icon
name=
"scale"
:size=
"16"
/>
<loading-icon
v-if=
"fetching"
class=
"js-weight-collapsed-loading-icon"
/>
<span
v-else
class=
"js-weight-collapsed-weight-label"
>
{{
collapsedWeightLabel
}}
</span>
</div>
<div
class=
"title hide-collapsed"
>
{{
s__
(
'
Sidebar|Weight
'
)
}}
<loading-icon
v-if=
"fetching || loading"
:inline=
"true"
class=
"js-weight-loading-icon"
/>
<a
v-if=
"editable"
class=
"pull-right js-weight-edit-link"
href=
"#"
@
click=
"showDropdown"
>
{{
s__
(
'
Sidebar|Edit
'
)
}}
</a>
</div>
<div
v-if=
"shouldShowWeight"
class=
"value hide-collapsed js-weight-weight-label"
>
<strong
v-if=
"!isNoValue"
>
{{
weight
}}
</strong>
<span
v-else
class=
"no-value"
>
{{
noValueLabel
}}
</span>
</div>
<div
class=
"selectbox hide-collapsed"
:class=
"
{ show: shouldShowDropdown }"
>
<div
ref=
"weightDropdown"
class=
"dropdown"
>
<button
ref=
"dropdownToggle"
class=
"dropdown-menu-toggle js-gl-dropdown-refresh-on-open"
type=
"button"
data-toggle=
"dropdown"
>
<span
class=
"dropdown-toggle-text js-weight-dropdown-toggle-text"
:class=
"
{ 'is-default': isNoValue }"
>
{{
dropdownToggleLabel
}}
</span>
<i
aria-hidden=
"true"
data-hidden=
"true"
class=
"fa fa-chevron-down"
></i>
</button>
<div
v-once
class=
"dropdown-menu dropdown-select dropdown-menu-selectable dropdown-menu-weight"
>
<div
class=
"dropdown-title"
>
<span>
{{
changeWeightLabel
}}
</span>
<button
class=
"dropdown-title-button dropdown-menu-close"
aria-label=
"Close"
type=
"button"
>
<i
aria-hidden=
"true"
data-hidden=
"true"
class=
"fa fa-times dropdown-menu-close-icon"
></i>
</button>
</div>
<div
class=
"dropdown-content js-weight-dropdown-content"
></div>
</div>
</div>
</div>
</div>
</
template
>
ee/app/assets/javascripts/sidebar/mount_sidebar.js
0 → 100644
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
sidebarWeight
from
'
./components/weight/sidebar_weight.vue
'
;
function
mountWeightComponent
(
mediator
)
{
const
el
=
document
.
querySelector
(
'
.js-sidebar-weight-entry-point
'
);
if
(
!
el
)
return
;
// eslint-disable-next-line no-new
new
Vue
({
el
,
components
:
{
sidebarWeight
,
},
render
:
createElement
=>
createElement
(
'
sidebar-weight
'
,
{
props
:
{
mediator
,
},
}),
});
}
function
mount
(
mediator
)
{
mountWeightComponent
(
mediator
);
}
export
default
mount
;
ee/app/assets/javascripts/sidebar/sidebar_mediator.js
0 → 100644
View file @
e5d48566
import
CESidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
Store
from
'
ee/sidebar/stores/sidebar_store
'
;
export
default
class
SidebarMediator
extends
CESidebarMediator
{
initSingleton
(
options
)
{
super
.
initSingleton
(
options
);
this
.
store
=
new
Store
(
options
);
}
processFetchedData
(
data
)
{
super
.
processFetchedData
(
data
);
this
.
store
.
setWeightData
(
data
);
}
updateWeight
(
newWeight
)
{
this
.
store
.
setLoadingState
(
'
weight
'
,
true
);
return
this
.
service
.
update
(
'
issue[weight]
'
,
newWeight
)
.
then
(
res
=>
res
.
json
())
.
then
((
data
)
=>
{
this
.
store
.
setWeight
(
data
.
weight
);
this
.
store
.
setLoadingState
(
'
weight
'
,
false
);
})
.
catch
((
err
)
=>
{
this
.
store
.
setLoadingState
(
'
weight
'
,
false
);
throw
err
;
});
}
}
ee/app/assets/javascripts/sidebar/stores/sidebar_store.js
0 → 100644
View file @
e5d48566
import
CESidebarStore
from
'
~/sidebar/stores/sidebar_store
'
;
export
default
class
SidebarStore
extends
CESidebarStore
{
constructor
(
store
)
{
super
(
store
);
this
.
isFetching
.
weight
=
true
;
this
.
isLoading
.
weight
=
false
;
this
.
weight
=
null
;
this
.
weightOptions
=
store
.
weightOptions
;
this
.
weightNoneValue
=
store
.
weightNoneValue
;
}
setWeightData
(
data
)
{
this
.
isFetching
.
weight
=
false
;
this
.
weight
=
data
.
weight
||
null
;
}
setWeight
(
newWeight
)
{
this
.
weight
=
newWeight
;
}
}
ee/app/serializers/ee/issuable_sidebar_entity.rb
0 → 100644
View file @
e5d48566
module
EE
module
IssuableSidebarEntity
extend
ActiveSupport
::
Concern
prepended
do
expose
:weight
,
if:
->
(
issuable
,
options
)
{
issuable
.
supports_weight?
}
end
end
end
ee/app/views/shared/boards/components/sidebar/_weight.html.haml
0 → 100644
View file @
e5d48566
%weight
{
":fetching"
=>
"issue.isFetching && issue.isFetching.weight"
,
":loading"
=>
"issue.isLoading && issue.isLoading.weight"
,
":weight"
=>
"issue.weight"
,
":weight-options"
=>
Issue
.
weight_options
,
"weight-none-value"
=>
Issue
::
WEIGHT_NONE
,
":editable"
=>
can_admin_issue?
,
":id"
=>
"issue.id"
}
spec/ee/spec/features/boards/sidebar_spec.rb
View file @
e5d48566
...
...
@@ -9,11 +9,12 @@ describe 'Issue Boards', :js do
let!
(
:milestone
)
{
create
(
:milestone
,
project:
project
)
}
let!
(
:development
)
{
create
(
:label
,
project:
project
,
name:
'Development'
)
}
let!
(
:stretch
)
{
create
(
:label
,
project:
project
,
name:
'Stretch'
)
}
let!
(
:issue1
)
{
create
(
:labeled_issue
,
project:
project
,
assignees:
[
user
],
milestone:
milestone
,
labels:
[
development
],
relative_position:
2
)
}
let!
(
:issue1
)
{
create
(
:labeled_issue
,
project:
project
,
assignees:
[
user
],
milestone:
milestone
,
labels:
[
development
],
weight:
3
,
relative_position:
2
)
}
let!
(
:issue2
)
{
create
(
:labeled_issue
,
project:
project
,
labels:
[
development
,
stretch
],
relative_position:
1
)
}
let
(
:board
)
{
create
(
:board
,
project:
project
)
}
let!
(
:list
)
{
create
(
:list
,
board:
board
,
label:
development
,
position:
0
)
}
let
(
:card
)
{
find
(
'.board:nth-child(2)'
).
first
(
'.card'
)
}
let
(
:card1
)
{
find
(
'.board:nth-child(2)'
).
find
(
'.card:nth-child(2)'
)
}
let
(
:card2
)
{
find
(
'.board:nth-child(2)'
).
find
(
'.card:nth-child(1)'
)
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
...
...
@@ -33,7 +34,7 @@ describe 'Issue Boards', :js do
context
'assignee'
do
it
'updates the issues assignee'
do
click_card
(
card
)
click_card
(
card
2
)
page
.
within
(
'.assignee'
)
do
click_link
'Edit'
...
...
@@ -49,11 +50,11 @@ describe 'Issue Boards', :js do
expect
(
page
).
to
have_content
(
user
.
name
)
end
expect
(
card
).
to
have_selector
(
'.avatar'
)
expect
(
card
2
).
to
have_selector
(
'.avatar'
)
end
it
'adds multiple assignees'
do
click_card
(
card
)
click_card
(
card
2
)
page
.
within
(
'.assignee'
)
do
click_link
'Edit'
...
...
@@ -69,7 +70,7 @@ describe 'Issue Boards', :js do
expect
(
page
).
to
have_content
(
user2
.
name
)
end
expect
(
card
.
all
(
'.avatar'
).
length
).
to
eq
(
2
)
expect
(
card
2
.
all
(
'.avatar'
).
length
).
to
eq
(
2
)
end
it
'removes the assignee'
do
...
...
@@ -96,7 +97,7 @@ describe 'Issue Boards', :js do
end
it
'assignees to current user'
do
click_card
(
card
)
click_card
(
card
2
)
page
.
within
(
find
(
'.assignee'
))
do
expect
(
page
).
to
have_content
(
'No assignee'
)
...
...
@@ -108,11 +109,11 @@ describe 'Issue Boards', :js do
expect
(
page
).
to
have_content
(
user
.
name
)
end
expect
(
card
).
to
have_selector
(
'.avatar'
)
expect
(
card
2
).
to
have_selector
(
'.avatar'
)
end
it
'updates assignee dropdown'
do
click_card
(
card
)
click_card
(
card
2
)
page
.
within
(
'.assignee'
)
do
click_link
'Edit'
...
...
@@ -139,4 +140,67 @@ describe 'Issue Boards', :js do
end
end
end
context
'weight'
do
it
'displays weight async'
do
click_card
(
card1
)
wait_for_requests
expect
(
find
(
'.js-weight-weight-label'
).
text
).
to
have_content
(
issue1
.
weight
)
end
it
'updates weight in sidebar to 1'
do
click_card
(
card1
)
wait_for_requests
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'1'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
end
end
# Ensure the request was sent and things are persisted
visit
project_board_path
(
project
,
board
)
wait_for_requests
click_card
(
card1
)
wait_for_requests
page
.
within
'.weight'
do
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'1'
end
end
end
it
'updates weight in sidebar to no weight'
do
click_card
(
card1
)
wait_for_requests
page
.
within
'.weight'
do
click_link
'Edit'
click_link
'No Weight'
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
end
end
# Ensure the request was sent and things are persisted
visit
project_board_path
(
project
,
board
)
wait_for_requests
click_card
(
card1
)
wait_for_requests
page
.
within
'.weight'
do
page
.
within
'.value'
do
expect
(
page
).
to
have_content
'None'
end
end
end
end
end
spec/fixtures/api/schemas/entities/issue_sidebar.json
View file @
e5d48566
...
...
@@ -15,7 +15,8 @@
"assignees"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"../public_api/v4/user/basic.json"
}
}
},
"weight"
:
{
"type"
:
[
"integer"
,
"null"
]
}
},
"additionalProperties"
:
false
}
spec/javascripts/boards/issue_spec.js
View file @
e5d48566
...
...
@@ -146,6 +146,12 @@ describe('Issue model', () => {
expect
(
issue
.
isFetching
.
subscriptions
).
toBe
(
false
);
});
it
(
'
sets loading state
'
,
()
=>
{
issue
.
setLoadingState
(
'
foo
'
,
true
);
expect
(
issue
.
isLoading
.
foo
).
toBe
(
true
);
});
describe
(
'
update
'
,
()
=>
{
it
(
'
passes assignee ids when there are assignees
'
,
(
done
)
=>
{
spyOn
(
Vue
.
http
,
'
patch
'
).
and
.
callFake
((
url
,
data
)
=>
{
...
...
spec/javascripts/sidebar/ee_mock_data.js
0 → 100644
View file @
e5d48566
import
CEMockData
from
'
./mock_data
'
;
const
RESPONSE_MAP
=
{
...
CEMockData
.
responseMap
};
RESPONSE_MAP
.
GET
[
'
/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar
'
]
=
{
assignees
:
[
{
name
:
'
User 0
'
,
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
name
:
'
Marguerite Bartell
'
,
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
name
:
'
Laureen Ritchie
'
,
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
human_time_estimate
:
null
,
human_total_time_spent
:
null
,
participants
:
[
{
name
:
'
User 0
'
,
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
name
:
'
Marguerite Bartell
'
,
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
name
:
'
Laureen Ritchie
'
,
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
subscribed
:
true
,
time_estimate
:
0
,
total_time_spent
:
0
,
weight
:
3
,
};
export
default
{
...
CEMockData
,
mediator
:
{
...
CEMockData
.
mediator
,
weightOptions
:
[
'
No Weight
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No Weight
'
,
},
responseMap
:
RESPONSE_MAP
,
};
spec/javascripts/sidebar/ee_sidebar_mediator_spec.js
0 → 100644
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
SidebarMediator
from
'
ee/sidebar/sidebar_mediator
'
;
import
SidebarStore
from
'
ee/sidebar/stores/sidebar_store
'
;
import
SidebarService
from
'
~/sidebar/services/sidebar_service
'
;
import
Mock
from
'
./ee_mock_data
'
;
describe
(
'
EE Sidebar mediator
'
,
()
=>
{
beforeEach
(()
=>
{
Vue
.
http
.
interceptors
.
push
(
Mock
.
sidebarMockInterceptor
);
this
.
mediator
=
new
SidebarMediator
(
Mock
.
mediator
);
});
afterEach
(()
=>
{
SidebarService
.
singleton
=
null
;
SidebarStore
.
singleton
=
null
;
SidebarMediator
.
singleton
=
null
;
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
Mock
.
sidebarMockInterceptor
);
});
it
(
'
processes fetched data
'
,
()
=>
{
const
mockData
=
Mock
.
responseMap
.
GET
[
'
/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar
'
];
this
.
mediator
.
processFetchedData
(
mockData
);
expect
(
this
.
mediator
.
store
.
weight
).
toEqual
(
mockData
.
weight
);
});
});
spec/javascripts/sidebar/ee_sidebar_store_spec.js
0 → 100644
View file @
e5d48566
import
SidebarStore
from
'
ee/sidebar/stores/sidebar_store
'
;
describe
(
'
EE Sidebar store
'
,
()
=>
{
beforeEach
(()
=>
{
this
.
store
=
new
SidebarStore
({
weightOptions
:
[
'
No Weight
'
,
0
,
1
,
3
],
weightNoneValue
:
'
No Weight
'
,
});
});
afterEach
(()
=>
{
SidebarStore
.
singleton
=
null
;
});
it
(
'
sets weight data
'
,
()
=>
{
expect
(
this
.
store
.
weight
).
toEqual
(
null
);
const
weight
=
3
;
this
.
store
.
setWeightData
({
weight
,
});
expect
(
this
.
store
.
isFetching
.
weight
).
toEqual
(
false
);
expect
(
this
.
store
.
weight
).
toEqual
(
weight
);
});
it
(
'
set weight
'
,
()
=>
{
const
weight
=
3
;
this
.
store
.
setWeight
(
weight
);
expect
(
this
.
store
.
weight
).
toEqual
(
weight
);
});
});
spec/javascripts/sidebar/mock_data.js
View file @
e5d48566
/* eslint-disable quote-props*/
const
sidebarMockData
=
{
const
RESPONSE_MAP
=
{
'
GET
'
:
{
'
/gitlab-org/gitlab-shell/issues/5.json
'
:
{
id
:
45
,
...
...
@@ -66,6 +66,65 @@ const sidebarMockData = {
},
labels
:
[],
},
'
/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar
'
:
{
assignees
:
[
{
name
:
'
User 0
'
,
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
name
:
'
Marguerite Bartell
'
,
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
name
:
'
Laureen Ritchie
'
,
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
human_time_estimate
:
null
,
human_total_time_spent
:
null
,
participants
:
[
{
name
:
'
User 0
'
,
username
:
'
user0
'
,
id
:
22
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/52e4ce24a915fb7e51e1ad3b57f4b00a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/user0
'
,
},
{
name
:
'
Marguerite Bartell
'
,
username
:
'
tajuana
'
,
id
:
18
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/4852a41fb41616bf8f140d3701673f53?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/tajuana
'
,
},
{
name
:
'
Laureen Ritchie
'
,
username
:
'
michaele.will
'
,
id
:
16
,
state
:
'
active
'
,
avatar_url
:
'
http: //www.gravatar.com/avatar/e301827eb03be955c9c172cb9a8e4e8a?s=80
\
u0026d=identicon
'
,
web_url
:
'
http: //localhost:3001/michaele.will
'
,
},
],
subscribed
:
true
,
time_estimate
:
0
,
total_time_spent
:
0
,
},
'
/autocomplete/projects?project_id=15
'
:
[
{
'
id
'
:
0
,
...
...
@@ -113,9 +172,10 @@ const sidebarMockData = {
},
};
export
default
{
const
mockData
=
{
responseMap
:
RESPONSE_MAP
,
mediator
:
{
endpoint
:
'
/gitlab-org/gitlab-shell/issues/5.json
'
,
endpoint
:
'
/gitlab-org/gitlab-shell/issues/5.json
?serializer=sidebar
'
,
toggleSubscriptionEndpoint
:
'
/gitlab-org/gitlab-shell/issues/5/toggle_subscription
'
,
moveIssueEndpoint
:
'
/gitlab-org/gitlab-shell/issues/5/move
'
,
projectsAutocompleteEndpoint
:
'
/autocomplete/projects?project_id=15
'
,
...
...
@@ -141,12 +201,14 @@ export default {
name
:
'
Administrator
'
,
username
:
'
root
'
,
},
};
sidebarMockInterceptor
(
request
,
next
)
{
const
body
=
sidebarMockData
[
request
.
method
.
toUpperCase
()][
request
.
url
];
mockData
.
sidebarMockInterceptor
=
function
(
request
,
next
)
{
const
body
=
this
.
responseMap
[
request
.
method
.
toUpperCase
()][
request
.
url
];
next
(
request
.
respondWith
(
JSON
.
stringify
(
body
),
{
status
:
200
,
}));
},
};
next
(
request
.
respondWith
(
JSON
.
stringify
(
body
),
{
status
:
200
,
}));
}.
bind
(
mockData
);
export
default
mockData
;
spec/javascripts/sidebar/sidebar_mediator_spec.js
View file @
e5d48566
...
...
@@ -33,10 +33,29 @@ describe('Sidebar mediator', () => {
.
catch
(
done
.
fail
);
});
it
(
'
fetches the data
'
,
()
=>
{
spyOn
(
this
.
mediator
.
service
,
'
get
'
).
and
.
callThrough
();
this
.
mediator
.
fetch
();
expect
(
this
.
mediator
.
service
.
get
).
toHaveBeenCalled
();
it
(
'
fetches the data
'
,
(
done
)
=>
{
const
mockData
=
Mock
.
responseMap
.
GET
[
'
/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar
'
];
spyOn
(
this
.
mediator
,
'
processFetchedData
'
).
and
.
callThrough
();
this
.
mediator
.
fetch
()
.
then
(()
=>
{
expect
(
this
.
mediator
.
processFetchedData
).
toHaveBeenCalledWith
(
mockData
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
processes fetched data
'
,
()
=>
{
const
mockData
=
Mock
.
responseMap
.
GET
[
'
/gitlab-org/gitlab-shell/issues/5.json?serializer=sidebar
'
];
this
.
mediator
.
processFetchedData
(
mockData
);
expect
(
this
.
mediator
.
store
.
assignees
).
toEqual
(
mockData
.
assignees
);
expect
(
this
.
mediator
.
store
.
humanTimeEstimate
).
toEqual
(
mockData
.
human_time_estimate
);
expect
(
this
.
mediator
.
store
.
humanTotalTimeSpent
).
toEqual
(
mockData
.
human_total_time_spent
);
expect
(
this
.
mediator
.
store
.
participants
).
toEqual
(
mockData
.
participants
);
expect
(
this
.
mediator
.
store
.
subscribed
).
toEqual
(
mockData
.
subscribed
);
expect
(
this
.
mediator
.
store
.
timeEstimate
).
toEqual
(
mockData
.
time_estimate
);
expect
(
this
.
mediator
.
store
.
totalTimeSpent
).
toEqual
(
mockData
.
total_time_spent
);
});
it
(
'
sets moveToProjectId
'
,
()
=>
{
...
...
spec/javascripts/sidebar/sidebar_store_spec.js
View file @
e5d48566
...
...
@@ -120,6 +120,12 @@ describe('Sidebar store', () => {
expect
(
this
.
store
.
isFetching
.
participants
).
toEqual
(
false
);
});
it
(
'
sets loading state
'
,
()
=>
{
this
.
store
.
setLoadingState
(
'
assignees
'
,
true
);
expect
(
this
.
store
.
isLoading
.
assignees
).
toEqual
(
true
);
});
it
(
'
set time tracking data
'
,
()
=>
{
this
.
store
.
setTimeTrackingData
(
Mock
.
time
);
expect
(
this
.
store
.
timeEstimate
).
toEqual
(
Mock
.
time
.
time_estimate
);
...
...
spec/javascripts/sidebar/sidebar_weight_spec.js
0 → 100644
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
sidebarWeight
from
'
ee/sidebar/components/weight/sidebar_weight.vue
'
;
import
SidebarMediator
from
'
ee/sidebar/sidebar_mediator
'
;
import
SidebarService
from
'
~/sidebar/services/sidebar_service
'
;
import
SidebarStore
from
'
ee/sidebar/stores/sidebar_store
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
import
Mock
from
'
./ee_mock_data
'
;
describe
(
'
Sidebar Weight
'
,
function
()
{
let
vm
;
let
sidebarMediator
;
let
SidebarWeight
;
beforeEach
(()
=>
{
SidebarWeight
=
Vue
.
extend
(
sidebarWeight
);
// Setup the stores, services, etc
sidebarMediator
=
new
SidebarMediator
(
Mock
.
mediator
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
SidebarService
.
singleton
=
null
;
SidebarStore
.
singleton
=
null
;
SidebarMediator
.
singleton
=
null
;
});
it
(
'
calls the mediator updateWeight on event
'
,
()
=>
{
spyOn
(
SidebarMediator
.
prototype
,
'
updateWeight
'
).
and
.
returnValue
(
Promise
.
resolve
());
vm
=
mountComponent
(
SidebarWeight
,
{
mediator
:
sidebarMediator
,
});
eventHub
.
$emit
(
'
updateWeight
'
);
expect
(
SidebarMediator
.
prototype
.
updateWeight
).
toHaveBeenCalled
();
});
});
spec/javascripts/sidebar/weight_spec.js
0 → 100644
View file @
e5d48566
import
Vue
from
'
vue
'
;
import
weight
from
'
ee/sidebar/components/weight/weight.vue
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
mountComponent
from
'
../helpers/vue_mount_component_helper
'
;
import
getSetTimeoutPromise
from
'
../helpers/set_timeout_promise_helper
'
;
const
DEFAULT_PROPS
=
{
weightOptions
:
[
'
No Weight
'
,
1
,
2
,
3
],
weightNoneValue
:
'
No Weight
'
,
};
describe
(
'
Weight
'
,
function
()
{
let
vm
;
let
Weight
;
beforeEach
(()
=>
{
Weight
=
Vue
.
extend
(
weight
);
});
afterEach
(()
=>
{
vm
.
$destroy
();
});
it
(
'
shows loading spinner when fetching
'
,
()
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
fetching
:
true
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-loading-icon
'
)).
not
.
toBeNull
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-loading-icon
'
)).
not
.
toBeNull
();
});
it
(
'
shows loading spinner when loading
'
,
()
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
fetching
:
false
,
loading
:
true
,
});
// We show the value in the collapsed view instead of the loading icon
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-loading-icon
'
)).
toBeNull
();
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-loading-icon
'
)).
not
.
toBeNull
();
});
it
(
'
shows weight value
'
,
()
=>
{
const
WEIGHT
=
3
;
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
fetching
:
false
,
weight
:
WEIGHT
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-toggle-text
'
).
textContent
.
trim
()).
toEqual
(
`
${
WEIGHT
}
`
);
});
it
(
'
shows weight no-value
'
,
()
=>
{
const
WEIGHT
=
null
;
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
fetching
:
false
,
weight
:
WEIGHT
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-weight-label
'
).
textContent
.
trim
()).
toEqual
(
'
No
'
);
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-weight-label
'
).
textContent
.
trim
()).
toEqual
(
'
None
'
);
// Put a placeholder in the dropdown toggle
expect
(
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-toggle-text
'
).
textContent
.
trim
()).
toEqual
(
'
Weight
'
);
});
it
(
'
adds `collapse-after-update` class when clicking the collapsed block
'
,
(
done
)
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-collapsed-block
'
).
click
();
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
collapse-after-update
'
)).
toEqual
(
true
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
shows dropdown on "Edit" link click
'
,
(
done
)
=>
{
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
});
expect
(
vm
.
shouldShowDropdown
).
toEqual
(
false
);
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
()
.
then
(()
=>
{
expect
(
vm
.
shouldShowDropdown
).
toEqual
(
true
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
it
(
'
emits event on dropdown item click
'
,
(
done
)
=>
{
const
ID
=
123
;
spyOn
(
eventHub
,
'
$emit
'
);
vm
=
mountComponent
(
Weight
,
{
...
DEFAULT_PROPS
,
editable
:
true
,
id
:
ID
,
});
vm
.
$el
.
querySelector
(
'
.js-weight-edit-link
'
).
click
();
vm
.
$nextTick
()
.
then
(()
=>
getSetTimeoutPromise
())
.
then
(()
=>
{
vm
.
$el
.
querySelector
(
'
.js-weight-dropdown-content li:nth-child(2) a
'
).
click
();
})
.
then
(()
=>
{
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
updateWeight
'
,
DEFAULT_PROPS
.
weightOptions
[
1
],
ID
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
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