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
f74f7061
Commit
f74f7061
authored
Jul 09, 2019
by
GitLab Bot
Browse files
Options
Browse Files
Download
Plain Diff
Automatic merge of gitlab-org/gitlab-ce master
parents
05a902dd
775910d3
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
334 additions
and
9 deletions
+334
-9
app/assets/javascripts/api.js
app/assets/javascripts/api.js
+6
-0
app/assets/javascripts/commons/index.js
app/assets/javascripts/commons/index.js
+3
-0
app/assets/javascripts/commons/nav/user_merge_requests.js
app/assets/javascripts/commons/nav/user_merge_requests.js
+67
-0
app/assets/javascripts/notes/components/comment_form.vue
app/assets/javascripts/notes/components/comment_form.vue
+9
-2
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
...cripts/sidebar/components/assignees/sidebar_assignees.vue
+4
-0
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
...merge_request_widget/components/states/ready_to_merge.vue
+3
-0
app/assets/stylesheets/framework/modal.scss
app/assets/stylesheets/framework/modal.scss
+2
-1
app/assets/stylesheets/framework/variables.scss
app/assets/stylesheets/framework/variables.scss
+1
-1
changelogs/unreleased/tz-update-mr-count-over-tabs.yml
changelogs/unreleased/tz-update-mr-count-over-tabs.yml
+6
-0
doc/api/users.md
doc/api/users.md
+24
-0
doc/workflow/timezone.md
doc/workflow/timezone.md
+3
-5
lib/api/api.rb
lib/api/api.rb
+1
-0
lib/api/user_counts.rb
lib/api/user_counts.rb
+18
-0
spec/frontend/api_spec.js
spec/frontend/api_spec.js
+16
-0
spec/frontend/commons/nav/user_merge_requests_spec.js
spec/frontend/commons/nav/user_merge_requests_spec.js
+113
-0
spec/javascripts/notes/components/comment_form_spec.js
spec/javascripts/notes/components/comment_form_spec.js
+15
-0
spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
...widget/components/states/mr_widget_ready_to_merge_spec.js
+3
-0
spec/requests/api/user_counts_spec.rb
spec/requests/api/user_counts_spec.rb
+40
-0
No files found.
app/assets/javascripts/api.js
View file @
f74f7061
...
...
@@ -24,6 +24,7 @@ const Api = {
issuableTemplatePath
:
'
/:namespace_path/:project_path/templates/:type/:key
'
,
projectTemplatePath
:
'
/api/:version/projects/:id/templates/:type/:key
'
,
projectTemplatesPath
:
'
/api/:version/projects/:id/templates/:type
'
,
userCountsPath
:
'
/api/:version/user_counts
'
,
usersPath
:
'
/api/:version/users.json
'
,
userPath
:
'
/api/:version/users/:id
'
,
userStatusPath
:
'
/api/:version/users/:id/status
'
,
...
...
@@ -312,6 +313,11 @@ const Api = {
});
},
userCounts
()
{
const
url
=
Api
.
buildUrl
(
this
.
userCountsPath
);
return
axios
.
get
(
url
);
},
userStatus
(
id
,
options
)
{
const
url
=
Api
.
buildUrl
(
this
.
userStatusPath
).
replace
(
'
:id
'
,
encodeURIComponent
(
id
));
return
axios
.
get
(
url
,
{
...
...
app/assets/javascripts/commons/index.js
View file @
f74f7061
...
...
@@ -4,3 +4,6 @@ import './jquery';
import
'
./bootstrap
'
;
import
'
./vue
'
;
import
'
../lib/utils/axios_utils
'
;
import
{
openUserCountsBroadcast
}
from
'
./nav/user_merge_requests
'
;
openUserCountsBroadcast
();
app/assets/javascripts/commons/nav/user_merge_requests.js
0 → 100644
View file @
f74f7061
import
Api
from
'
~/api
'
;
let
channel
;
function
broadcastCount
(
newCount
)
{
if
(
!
channel
)
{
return
;
}
channel
.
postMessage
(
newCount
);
}
function
updateUserMergeRequestCounts
(
newCount
)
{
const
mergeRequestsCountEl
=
document
.
querySelector
(
'
.merge-requests-count
'
);
mergeRequestsCountEl
.
textContent
=
newCount
.
toLocaleString
();
mergeRequestsCountEl
.
classList
.
toggle
(
'
hidden
'
,
Number
(
newCount
)
===
0
);
}
/**
* Refresh user counts (and broadcast if open)
*/
export
function
refreshUserMergeRequestCounts
()
{
return
Api
.
userCounts
()
.
then
(({
data
})
=>
{
const
count
=
data
.
merge_requests
;
updateUserMergeRequestCounts
(
count
);
broadcastCount
(
count
);
})
.
catch
(
ex
=>
{
console
.
error
(
ex
);
// eslint-disable-line no-console
});
}
/**
* Close the broadcast channel for user counts
*/
export
function
closeUserCountsBroadcast
()
{
if
(
!
channel
)
{
return
;
}
channel
.
close
();
channel
=
null
;
}
/**
* Open the broadcast channel for user counts, adds user id so we only update
*
* **Please note:**
* Not supported in all browsers, but not polyfilling for now
* to keep bundle size small and
* no special functionality lost except cross tab notifications
*/
export
function
openUserCountsBroadcast
()
{
closeUserCountsBroadcast
();
if
(
window
.
BroadcastChannel
)
{
const
currentUserId
=
typeof
gon
!==
'
undefined
'
&&
gon
&&
gon
.
current_user_id
;
if
(
currentUserId
)
{
channel
=
new
BroadcastChannel
(
`mr_count_channel_
${
currentUserId
}
`
);
channel
.
onmessage
=
ev
=>
{
updateUserMergeRequestCounts
(
ev
.
data
);
};
}
}
}
app/assets/javascripts/notes/components/comment_form.vue
View file @
f74f7061
...
...
@@ -13,6 +13,7 @@ import {
splitCamelCase
,
slugifyWithUnderscore
,
}
from
'
../../lib/utils/text_utility
'
;
import
{
refreshUserMergeRequestCounts
}
from
'
~/commons/nav/user_merge_requests
'
;
import
*
as
constants
from
'
../constants
'
;
import
eventHub
from
'
../event_hub
'
;
import
issueWarning
from
'
../../vue_shared/components/issue/issue_warning.vue
'
;
...
...
@@ -234,7 +235,10 @@ export default {
toggleIssueState
()
{
if
(
this
.
isOpen
)
{
this
.
closeIssue
()
.
then
(()
=>
this
.
enableButton
())
.
then
(()
=>
{
this
.
enableButton
();
refreshUserMergeRequestCounts
();
})
.
catch
(()
=>
{
this
.
enableButton
();
this
.
toggleStateButtonLoading
(
false
);
...
...
@@ -247,7 +251,10 @@ export default {
});
}
else
{
this
.
reopenIssue
()
.
then
(()
=>
this
.
enableButton
())
.
then
(()
=>
{
this
.
enableButton
();
refreshUserMergeRequestCounts
();
})
.
catch
(({
data
})
=>
{
this
.
enableButton
();
this
.
toggleStateButtonLoading
(
false
);
...
...
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
View file @
f74f7061
...
...
@@ -2,6 +2,7 @@
import
Flash
from
'
~/flash
'
;
import
eventHub
from
'
~/sidebar/event_hub
'
;
import
Store
from
'
~/sidebar/stores/sidebar_store
'
;
import
{
refreshUserMergeRequestCounts
}
from
'
~/commons/nav/user_merge_requests
'
;
import
AssigneeTitle
from
'
./assignee_title.vue
'
;
import
Assignees
from
'
./assignees.vue
'
;
import
{
__
}
from
'
~/locale
'
;
...
...
@@ -73,6 +74,9 @@ export default {
this
.
mediator
.
saveAssignees
(
this
.
field
)
.
then
(
setLoadingFalse
.
bind
(
this
))
.
then
(()
=>
{
refreshUserMergeRequestCounts
();
})
.
catch
(()
=>
{
setLoadingFalse
();
return
new
Flash
(
__
(
'
Error occurred when saving assignees
'
));
...
...
app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
View file @
f74f7061
...
...
@@ -6,6 +6,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import
{
__
}
from
'
~/locale
'
;
import
readyToMergeMixin
from
'
ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge
'
;
import
MergeRequest
from
'
../../../merge_request
'
;
import
{
refreshUserMergeRequestCounts
}
from
'
~/commons/nav/user_merge_requests
'
;
import
Flash
from
'
../../../flash
'
;
import
statusIcon
from
'
../mr_widget_status_icon.vue
'
;
import
eventHub
from
'
../../event_hub
'
;
...
...
@@ -174,6 +175,8 @@ export default {
MergeRequest
.
decreaseCounter
();
stopPolling
();
refreshUserMergeRequestCounts
();
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
if
(
this
.
removeSourceBranch
&&
data
.
source_branch_exists
)
{
...
...
app/assets/stylesheets/framework/modal.scss
View file @
f74f7061
...
...
@@ -35,7 +35,8 @@
background-color
:
$modal-body-bg
;
line-height
:
$line-height-base
;
position
:
relative
;
padding
:
#{
3
*
$grid-size
}
#{
2
*
$grid-size
}
;
min-height
:
$modal-body-height
;
padding
:
#{
2
*
$grid-size
}
#{
6
*
$grid-size
}
#{
2
*
$grid-size
}
#{
2
*
$grid-size
}
;
text-align
:
left
;
white-space
:
normal
;
...
...
app/assets/stylesheets/framework/variables.scss
View file @
f74f7061
...
...
@@ -805,7 +805,7 @@ $border-color-settings: #e1e1e1;
/*
Modals
*/
$modal-body-height
:
134
px
;
$modal-body-height
:
80
px
;
$modal-border-color
:
#e9ecef
;
$priority-label-empty-state-width
:
114px
;
...
...
changelogs/unreleased/tz-update-mr-count-over-tabs.yml
0 → 100644
View file @
f74f7061
---
title
:
New API for User Counts, updates on success of an MR the count on top and in
other tabs
merge_request
:
29441
author
:
type
:
added
doc/api/users.md
View file @
f74f7061
...
...
@@ -593,6 +593,30 @@ Example responses
}
```
## User counts
Get the counts (same as in top right menu) of the currently signed in user.
| Attribute | Type | Description |
| --------- | ---- | ----------- |
|
`merge_requests`
| number | Merge requests that are active and assigned to current user. |
```
GET /user_counts
```
```
bash
curl
--header
"PRIVATE-TOKEN: <your_access_token>"
"https://gitlab.example.com/user_counts"
```
Example response:
```
json
{
"merge_requests"
:
4
}
```
## List user projects
Please refer to the
[
List of user projects
](
projects.md#list-user-projects
)
.
...
...
doc/workflow/timezone.md
View file @
f74f7061
...
...
@@ -2,13 +2,12 @@
The global time zone configuration parameter can be changed in
`config/gitlab.yml`
:
```
```
text
# time_zone: 'UTC'
```
Uncomment and customize if you want to change the default time zone of the GitLab application.
## Viewing available timezones
To see all available time zones, run
`bundle exec rake time:zones:all`
.
...
...
@@ -26,14 +25,13 @@ To obtain a list of timezones, log in to your GitLab application server and run
To update, add the timezone that best applies to your location. For example:
```
```
ruby
gitlab_rails
[
'time_zone'
]
=
'America/New_York'
```
After adding the configuration parameter, reconfigure and restart your GitLab instance:
```
```
sh
gitlab-ctl reconfigure
gitlab-ctl restart
```
lib/api/api.rb
View file @
f74f7061
...
...
@@ -166,6 +166,7 @@ module API
mount
::
API
::
Templates
mount
::
API
::
Todos
mount
::
API
::
Triggers
mount
::
API
::
UserCounts
mount
::
API
::
Users
mount
::
API
::
Variables
mount
::
API
::
Version
...
...
lib/api/user_counts.rb
0 → 100644
View file @
f74f7061
# frozen_string_literal: true
module
API
class
UserCounts
<
Grape
::
API
resource
:user_counts
do
desc
'Return the user specific counts'
do
detail
'Open MR Count'
end
get
do
unauthorized!
unless
current_user
{
merge_requests:
current_user
.
assigned_open_merge_requests_count
}
end
end
end
end
spec/frontend/api_spec.js
View file @
f74f7061
...
...
@@ -412,6 +412,22 @@ describe('Api', () => {
});
});
describe
(
'
user counts
'
,
()
=>
{
it
(
'
fetches single user counts
'
,
done
=>
{
const
expectedUrl
=
`
${
dummyUrlRoot
}
/api/
${
dummyApiVersion
}
/user_counts`
;
mock
.
onGet
(
expectedUrl
).
reply
(
200
,
{
merge_requests
:
4
,
});
Api
.
userCounts
()
.
then
(({
data
})
=>
{
expect
(
data
.
merge_requests
).
toBe
(
4
);
})
.
then
(
done
)
.
catch
(
done
.
fail
);
});
});
describe
(
'
user status
'
,
()
=>
{
it
(
'
fetches single user status
'
,
done
=>
{
const
userId
=
'
123456
'
;
...
...
spec/frontend/commons/nav/user_merge_requests_spec.js
0 → 100644
View file @
f74f7061
import
{
openUserCountsBroadcast
,
closeUserCountsBroadcast
,
refreshUserMergeRequestCounts
,
}
from
'
~/commons/nav/user_merge_requests
'
;
import
Api
from
'
~/api
'
;
jest
.
mock
(
'
~/api
'
);
const
TEST_COUNT
=
1000
;
const
MR_COUNT_CLASS
=
'
merge-requests-count
'
;
describe
(
'
User Merge Requests
'
,
()
=>
{
let
channelMock
;
let
newBroadcastChannelMock
;
beforeEach
(()
=>
{
global
.
gon
.
current_user_id
=
123
;
channelMock
=
{
postMessage
:
jest
.
fn
(),
close
:
jest
.
fn
(),
};
newBroadcastChannelMock
=
jest
.
fn
().
mockImplementation
(()
=>
channelMock
);
global
.
BroadcastChannel
=
newBroadcastChannelMock
;
setFixtures
(
`<div class="
${
MR_COUNT_CLASS
}
">0</div>`
);
});
const
findMRCountText
=
()
=>
document
.
body
.
querySelector
(
`.
${
MR_COUNT_CLASS
}
`
).
textContent
;
describe
(
'
refreshUserMergeRequestCounts
'
,
()
=>
{
beforeEach
(()
=>
{
Api
.
userCounts
.
mockReturnValue
(
Promise
.
resolve
({
data
:
{
merge_requests
:
TEST_COUNT
},
}),
);
});
describe
(
'
with open broadcast channel
'
,
()
=>
{
beforeEach
(()
=>
{
openUserCountsBroadcast
();
return
refreshUserMergeRequestCounts
();
});
it
(
'
updates the top count of merge requests
'
,
()
=>
{
expect
(
findMRCountText
()).
toEqual
(
TEST_COUNT
.
toLocaleString
());
});
it
(
'
calls the API
'
,
()
=>
{
expect
(
Api
.
userCounts
).
toHaveBeenCalled
();
});
it
(
'
posts count to BroadcastChannel
'
,
()
=>
{
expect
(
channelMock
.
postMessage
).
toHaveBeenCalledWith
(
TEST_COUNT
);
});
});
describe
(
'
without open broadcast channel
'
,
()
=>
{
beforeEach
(()
=>
refreshUserMergeRequestCounts
());
it
(
'
does not post anything
'
,
()
=>
{
expect
(
channelMock
.
postMessage
).
not
.
toHaveBeenCalled
();
});
});
});
describe
(
'
openUserCountsBroadcast
'
,
()
=>
{
beforeEach
(()
=>
{
openUserCountsBroadcast
();
});
it
(
'
creates BroadcastChannel that updates DOM on message received
'
,
()
=>
{
expect
(
findMRCountText
()).
toEqual
(
'
0
'
);
channelMock
.
onmessage
({
data
:
TEST_COUNT
});
expect
(
findMRCountText
()).
toEqual
(
TEST_COUNT
.
toLocaleString
());
});
it
(
'
closes if called while already open
'
,
()
=>
{
expect
(
channelMock
.
close
).
not
.
toHaveBeenCalled
();
openUserCountsBroadcast
();
expect
(
channelMock
.
close
).
toHaveBeenCalled
();
});
});
describe
(
'
closeUserCountsBroadcast
'
,
()
=>
{
describe
(
'
when not opened
'
,
()
=>
{
it
(
'
does nothing
'
,
()
=>
{
expect
(
channelMock
.
close
).
not
.
toHaveBeenCalled
();
});
});
describe
(
'
when opened
'
,
()
=>
{
beforeEach
(()
=>
{
openUserCountsBroadcast
();
});
it
(
'
closes
'
,
()
=>
{
expect
(
channelMock
.
close
).
not
.
toHaveBeenCalled
();
closeUserCountsBroadcast
();
expect
(
channelMock
.
close
).
toHaveBeenCalled
();
});
});
});
});
spec/javascripts/notes/components/comment_form_spec.js
View file @
f74f7061
...
...
@@ -251,6 +251,21 @@ describe('issue_comment_form component', () => {
});
});
});
describe
(
'
when toggling state
'
,
()
=>
{
it
(
'
should update MR count
'
,
done
=>
{
spyOn
(
vm
,
'
closeIssue
'
).
and
.
returnValue
(
Promise
.
resolve
());
const
updateMrCountSpy
=
spyOnDependency
(
CommentForm
,
'
refreshUserMergeRequestCounts
'
);
vm
.
toggleIssueState
();
Vue
.
nextTick
(()
=>
{
expect
(
updateMrCountSpy
).
toHaveBeenCalled
();
done
();
});
});
});
});
describe
(
'
issue is confidential
'
,
()
=>
{
...
...
spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
View file @
f74f7061
...
...
@@ -58,9 +58,11 @@ const createComponent = (customConfig = {}) => {
describe
(
'
ReadyToMerge
'
,
()
=>
{
let
vm
;
let
updateMrCountSpy
;
beforeEach
(()
=>
{
vm
=
createComponent
();
updateMrCountSpy
=
spyOnDependency
(
ReadyToMerge
,
'
refreshUserMergeRequestCounts
'
);
});
afterEach
(()
=>
{
...
...
@@ -461,6 +463,7 @@ describe('ReadyToMerge', () => {
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
MRWidgetUpdateRequested
'
);
expect
(
eventHub
.
$emit
).
toHaveBeenCalledWith
(
'
FetchActionsContent
'
);
expect
(
vm
.
initiateRemoveSourceBranchPolling
).
toHaveBeenCalled
();
expect
(
updateMrCountSpy
).
toHaveBeenCalled
();
expect
(
cpc
).
toBeFalsy
();
expect
(
spc
).
toBeTruthy
();
...
...
spec/requests/api/user_counts_spec.rb
0 → 100644
View file @
f74f7061
# frozen_string_literal: true
require
'spec_helper'
describe
API
::
UserCounts
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
,
:simple
,
author:
user
,
assignees:
[
user
],
source_project:
project
,
title:
"Test"
)
}
describe
'GET /user_counts'
do
context
'when unauthenticated'
do
it
'returns authentication error'
do
get
api
(
'/user_counts'
)
expect
(
response
.
status
).
to
eq
(
401
)
end
end
context
'when authenticated'
do
it
'returns open counts for current user'
do
get
api
(
'/user_counts'
,
user
)
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
json_response
).
to
be_a
Hash
expect
(
json_response
[
'merge_requests'
]).
to
eq
(
1
)
end
it
'updates the mr count when a new mr is assigned'
do
create
(
:merge_request
,
source_project:
project
,
author:
user
,
assignees:
[
user
])
get
api
(
'/user_counts'
,
user
)
expect
(
response
.
status
).
to
eq
(
200
)
expect
(
json_response
).
to
be_a
Hash
expect
(
json_response
[
'merge_requests'
]).
to
eq
(
2
)
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