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
06413304
Commit
06413304
authored
Apr 26, 2021
by
Heinrich Lee Yu
Committed by
Natalia Tepluhina
Apr 26, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Change real-time assignees to use GraphQL subscriptions
parent
f247acf9
Changes
15
Show whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
298 additions
and
122 deletions
+298
-122
app/assets/javascripts/actioncable_link.js
app/assets/javascripts/actioncable_link.js
+40
-0
app/assets/javascripts/lib/graphql.js
app/assets/javascripts/lib/graphql.js
+16
-3
app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
...ripts/sidebar/components/assignees/assignees_realtime.vue
+41
-38
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
...cripts/sidebar/components/assignees/sidebar_assignees.vue
+12
-2
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
...sidebar/components/assignees/sidebar_assignees_widget.vue
+7
-2
app/assets/javascripts/sidebar/constants.js
app/assets/javascripts/sidebar/constants.js
+2
-0
app/assets/javascripts/sidebar/mount_sidebar.js
app/assets/javascripts/sidebar/mount_sidebar.js
+4
-2
app/assets/javascripts/sidebar/queries/issuable_assignees.subscription.graphql
...s/sidebar/queries/issuable_assignees.subscription.graphql
+16
-0
app/helpers/issuables_helper.rb
app/helpers/issuables_helper.rb
+1
-0
spec/features/action_cable_logging_spec.rb
spec/features/action_cable_logging_spec.rb
+1
-5
spec/frontend/actioncable_link_spec.js
spec/frontend/actioncable_link_spec.js
+110
-0
spec/frontend/sidebar/assignees_realtime_spec.js
spec/frontend/sidebar/assignees_realtime_spec.js
+38
-70
spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
...bar/components/assignees/sidebar_assignees_widget_spec.js
+3
-0
spec/frontend/sidebar/mock_data.js
spec/frontend/sidebar/mock_data.js
+6
-0
spec/frontend/sidebar/sidebar_assignees_spec.js
spec/frontend/sidebar/sidebar_assignees_spec.js
+1
-0
No files found.
app/assets/javascripts/actioncable_link.js
0 → 100644
View file @
06413304
import
{
ApolloLink
,
Observable
}
from
'
apollo-link
'
;
import
{
print
}
from
'
graphql
'
;
import
cable
from
'
~/actioncable_consumer
'
;
import
{
uuids
}
from
'
~/diffs/utils/uuids
'
;
export
default
class
ActionCableLink
extends
ApolloLink
{
// eslint-disable-next-line class-methods-use-this
request
(
operation
)
{
return
new
Observable
((
observer
)
=>
{
const
subscription
=
cable
.
subscriptions
.
create
(
{
channel
:
'
GraphqlChannel
'
,
query
:
operation
.
query
?
print
(
operation
.
query
)
:
null
,
variables
:
operation
.
variables
,
operationName
:
operation
.
operationName
,
nonce
:
uuids
()[
0
],
},
{
received
(
data
)
{
if
(
data
.
errors
)
{
observer
.
error
(
data
.
errors
);
}
else
if
(
data
.
result
)
{
observer
.
next
(
data
.
result
);
}
if
(
!
data
.
more
)
{
observer
.
complete
();
}
},
},
);
return
{
unsubscribe
()
{
subscription
.
unsubscribe
();
},
};
});
}
}
app/assets/javascripts/lib/graphql.js
View file @
06413304
...
@@ -4,6 +4,7 @@ import { ApolloLink } from 'apollo-link';
...
@@ -4,6 +4,7 @@ import { ApolloLink } from 'apollo-link';
import
{
BatchHttpLink
}
from
'
apollo-link-batch-http
'
;
import
{
BatchHttpLink
}
from
'
apollo-link-batch-http
'
;
import
{
createHttpLink
}
from
'
apollo-link-http
'
;
import
{
createHttpLink
}
from
'
apollo-link-http
'
;
import
{
createUploadLink
}
from
'
apollo-upload-client
'
;
import
{
createUploadLink
}
from
'
apollo-upload-client
'
;
import
ActionCableLink
from
'
~/actioncable_link
'
;
import
{
apolloCaptchaLink
}
from
'
~/captcha/apollo_captcha_link
'
;
import
{
apolloCaptchaLink
}
from
'
~/captcha/apollo_captcha_link
'
;
import
{
StartupJSLink
}
from
'
~/lib/utils/apollo_startup_js_link
'
;
import
{
StartupJSLink
}
from
'
~/lib/utils/apollo_startup_js_link
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
import
csrf
from
'
~/lib/utils/csrf
'
;
...
@@ -83,15 +84,27 @@ export default (resolvers = {}, config = {}) => {
...
@@ -83,15 +84,27 @@ export default (resolvers = {}, config = {}) => {
});
});
});
});
return
new
ApolloClient
({
const
hasSubscriptionOperation
=
({
query
:
{
definitions
}
})
=>
{
typeDefs
,
return
definitions
.
some
(
link
:
ApolloLink
.
from
([
({
kind
,
operation
})
=>
kind
===
'
OperationDefinition
'
&&
operation
===
'
subscription
'
,
);
};
const
appLink
=
ApolloLink
.
split
(
hasSubscriptionOperation
,
new
ActionCableLink
(),
ApolloLink
.
from
([
requestCounterLink
,
requestCounterLink
,
performanceBarLink
,
performanceBarLink
,
new
StartupJSLink
(),
new
StartupJSLink
(),
apolloCaptchaLink
,
apolloCaptchaLink
,
uploadsLink
,
uploadsLink
,
]),
]),
);
return
new
ApolloClient
({
typeDefs
,
link
:
appLink
,
cache
:
new
InMemoryCache
({
cache
:
new
InMemoryCache
({
...
cacheConfig
,
...
cacheConfig
,
freezeResults
:
assumeImmutableResults
,
freezeResults
:
assumeImmutableResults
,
...
...
app/assets/javascripts/sidebar/components/assignees/assignees_realtime.vue
View file @
06413304
<
script
>
<
script
>
import
actionCable
from
'
~/actioncable_consumer
'
;
import
produce
from
'
immer
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
convertToGraphQLId
,
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
IssuableType
}
from
'
~/issue_show/constants
'
;
import
{
assigneesQueries
}
from
'
~/sidebar/constants
'
;
import
{
assigneesQueries
}
from
'
~/sidebar/constants
'
;
export
default
{
export
default
{
...
@@ -12,60 +13,62 @@ export default {
...
@@ -12,60 +13,62 @@ export default {
required
:
false
,
required
:
false
,
default
:
null
,
default
:
null
,
},
},
issuable
Iid
:
{
issuable
Type
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
projectPath
:
{
issuableId
:
{
type
:
String
,
type
:
Number
,
required
:
true
,
required
:
true
,
},
},
issuableType
:
{
queryVariables
:
{
type
:
String
,
type
:
Object
,
required
:
true
,
required
:
true
,
},
},
},
},
computed
:
{
issuableClass
()
{
return
Object
.
keys
(
IssuableType
).
find
((
key
)
=>
IssuableType
[
key
]
===
this
.
issuableType
);
},
},
apollo
:
{
apollo
:
{
workspac
e
:
{
issuabl
e
:
{
query
()
{
query
()
{
return
assigneesQueries
[
this
.
issuableType
].
query
;
return
assigneesQueries
[
this
.
issuableType
].
query
;
},
},
variables
()
{
return
this
.
queryVariables
;
},
update
(
data
)
{
return
data
.
workspace
?.
issuable
;
},
subscribeToMore
:
{
document
()
{
return
assigneesQueries
[
this
.
issuableType
].
subscription
;
},
variables
()
{
variables
()
{
return
{
return
{
iid
:
this
.
issuableIid
,
issuableId
:
convertToGraphQLId
(
this
.
issuableClass
,
this
.
issuableId
),
fullPath
:
this
.
projectPath
,
};
};
},
},
result
(
data
)
{
updateQuery
(
prev
,
{
subscriptionData
})
{
if
(
prev
&&
subscriptionData
?.
data
?.
issuableAssigneesUpdated
)
{
const
data
=
produce
(
prev
,
(
draftData
)
=>
{
draftData
.
workspace
.
issuable
.
assignees
.
nodes
=
subscriptionData
.
data
.
issuableAssigneesUpdated
.
assignees
.
nodes
;
});
if
(
this
.
mediator
)
{
if
(
this
.
mediator
)
{
this
.
handleFetchResult
(
data
);
this
.
handleFetchResult
(
data
);
}
}
return
data
;
}
return
prev
;
},
},
},
},
},
},
mounted
()
{
this
.
initActionCablePolling
();
},
beforeDestroy
()
{
this
.
$options
.
subscription
.
unsubscribe
();
},
},
methods
:
{
methods
:
{
received
(
data
)
{
handleFetchResult
(
data
)
{
if
(
data
.
event
===
'
updated
'
)
{
this
.
$apollo
.
queries
.
workspace
.
refetch
();
}
},
initActionCablePolling
()
{
this
.
$options
.
subscription
=
actionCable
.
subscriptions
.
create
(
{
channel
:
'
IssuesChannel
'
,
project_path
:
this
.
projectPath
,
iid
:
this
.
issuableIid
,
},
{
received
:
this
.
received
},
);
},
handleFetchResult
({
data
})
{
const
{
nodes
}
=
data
.
workspace
.
issuable
.
assignees
;
const
{
nodes
}
=
data
.
workspace
.
issuable
.
assignees
;
const
assignees
=
nodes
.
map
((
n
)
=>
({
const
assignees
=
nodes
.
map
((
n
)
=>
({
...
...
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
View file @
06413304
...
@@ -44,6 +44,10 @@ export default {
...
@@ -44,6 +44,10 @@ export default {
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
issuableId
:
{
type
:
Number
,
required
:
true
,
},
assigneeAvailabilityStatus
:
{
assigneeAvailabilityStatus
:
{
type
:
Object
,
type
:
Object
,
required
:
false
,
required
:
false
,
...
@@ -61,6 +65,12 @@ export default {
...
@@ -61,6 +65,12 @@ export default {
// Note: Realtime is only available on issues right now, future support for MR wil be built later.
// Note: Realtime is only available on issues right now, future support for MR wil be built later.
return
this
.
glFeatures
.
realTimeIssueSidebar
&&
this
.
issuableType
===
'
issue
'
;
return
this
.
glFeatures
.
realTimeIssueSidebar
&&
this
.
issuableType
===
'
issue
'
;
},
},
queryVariables
()
{
return
{
iid
:
this
.
issuableIid
,
fullPath
:
this
.
projectPath
,
};
},
relativeUrlRoot
()
{
relativeUrlRoot
()
{
return
gon
.
relative_url_root
??
''
;
return
gon
.
relative_url_root
??
''
;
},
},
...
@@ -121,9 +131,9 @@ export default {
...
@@ -121,9 +131,9 @@ export default {
<div>
<div>
<assignees-realtime
<assignees-realtime
v-if=
"shouldEnableRealtime"
v-if=
"shouldEnableRealtime"
:issuable-iid=
"issuableIid"
:project-path=
"projectPath"
:issuable-type=
"issuableType"
:issuable-type=
"issuableType"
:issuable-id=
"issuableId"
:query-variables=
"queryVariables"
:mediator=
"mediator"
:mediator=
"mediator"
/>
/>
<assignee-title
<assignee-title
...
...
app/assets/javascripts/sidebar/components/assignees/sidebar_assignees_widget.vue
View file @
06413304
...
@@ -73,6 +73,11 @@ export default {
...
@@ -73,6 +73,11 @@ export default {
return
[
IssuableType
.
Issue
,
IssuableType
.
MergeRequest
].
includes
(
value
);
return
[
IssuableType
.
Issue
,
IssuableType
.
MergeRequest
].
includes
(
value
);
},
},
},
},
issuableId
:
{
type
:
Number
,
required
:
false
,
default
:
null
,
},
multipleAssignees
:
{
multipleAssignees
:
{
type
:
Boolean
,
type
:
Boolean
,
required
:
false
,
required
:
false
,
...
@@ -340,9 +345,9 @@ export default {
...
@@ -340,9 +345,9 @@ export default {
<div
data-testid=
"assignees-widget"
>
<div
data-testid=
"assignees-widget"
>
<sidebar-assignees-realtime
<sidebar-assignees-realtime
v-if=
"shouldEnableRealtime"
v-if=
"shouldEnableRealtime"
:project-path=
"fullPath"
:issuable-iid=
"iid"
:issuable-type=
"issuableType"
:issuable-type=
"issuableType"
:issuable-id=
"issuableId"
:query-variables=
"queryVariables"
/>
/>
<sidebar-editable-item
<sidebar-editable-item
ref=
"toggle"
ref=
"toggle"
...
...
app/assets/javascripts/sidebar/constants.js
View file @
06413304
import
{
IssuableType
}
from
'
~/issue_show/constants
'
;
import
{
IssuableType
}
from
'
~/issue_show/constants
'
;
import
epicConfidentialQuery
from
'
~/sidebar/queries/epic_confidential.query.graphql
'
;
import
epicConfidentialQuery
from
'
~/sidebar/queries/epic_confidential.query.graphql
'
;
import
issuableAssigneesSubscription
from
'
~/sidebar/queries/issuable_assignees.subscription.graphql
'
;
import
issueConfidentialQuery
from
'
~/sidebar/queries/issue_confidential.query.graphql
'
;
import
issueConfidentialQuery
from
'
~/sidebar/queries/issue_confidential.query.graphql
'
;
import
issueDueDateQuery
from
'
~/sidebar/queries/issue_due_date.query.graphql
'
;
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
'
;
...
@@ -17,6 +18,7 @@ export const ASSIGNEES_DEBOUNCE_DELAY = 250;
...
@@ -17,6 +18,7 @@ export const ASSIGNEES_DEBOUNCE_DELAY = 250;
export
const
assigneesQueries
=
{
export
const
assigneesQueries
=
{
[
IssuableType
.
Issue
]:
{
[
IssuableType
.
Issue
]:
{
query
:
getIssueParticipants
,
query
:
getIssueParticipants
,
subscription
:
issuableAssigneesSubscription
,
mutation
:
updateAssigneesMutation
,
mutation
:
updateAssigneesMutation
,
},
},
[
IssuableType
.
MergeRequest
]:
{
[
IssuableType
.
MergeRequest
]:
{
...
...
app/assets/javascripts/sidebar/mount_sidebar.js
View file @
06413304
...
@@ -53,7 +53,7 @@ function mountAssigneesComponentDeprecated(mediator) {
...
@@ -53,7 +53,7 @@ function mountAssigneesComponentDeprecated(mediator) {
if
(
!
el
)
return
;
if
(
!
el
)
return
;
const
{
iid
,
fullPath
}
=
getSidebarOptions
();
const
{
i
d
,
i
id
,
fullPath
}
=
getSidebarOptions
();
const
assigneeAvailabilityStatus
=
getSidebarAssigneeAvailabilityData
();
const
assigneeAvailabilityStatus
=
getSidebarAssigneeAvailabilityData
();
// eslint-disable-next-line no-new
// eslint-disable-next-line no-new
new
Vue
({
new
Vue
({
...
@@ -74,6 +74,7 @@ function mountAssigneesComponentDeprecated(mediator) {
...
@@ -74,6 +74,7 @@ function mountAssigneesComponentDeprecated(mediator) {
isInIssuePage
()
||
isInIncidentPage
()
||
isInDesignPage
()
isInIssuePage
()
||
isInIncidentPage
()
||
isInDesignPage
()
?
IssuableType
.
Issue
?
IssuableType
.
Issue
:
IssuableType
.
MergeRequest
,
:
IssuableType
.
MergeRequest
,
issuableId
:
id
,
assigneeAvailabilityStatus
,
assigneeAvailabilityStatus
,
},
},
}),
}),
...
@@ -85,7 +86,7 @@ function mountAssigneesComponent() {
...
@@ -85,7 +86,7 @@ function mountAssigneesComponent() {
if
(
!
el
)
return
;
if
(
!
el
)
return
;
const
{
iid
,
fullPath
,
editable
,
projectMembersPath
}
=
getSidebarOptions
();
const
{
i
d
,
i
id
,
fullPath
,
editable
,
projectMembersPath
}
=
getSidebarOptions
();
// eslint-disable-next-line no-new
// eslint-disable-next-line no-new
new
Vue
({
new
Vue
({
el
,
el
,
...
@@ -108,6 +109,7 @@ function mountAssigneesComponent() {
...
@@ -108,6 +109,7 @@ function mountAssigneesComponent() {
isInIssuePage
()
||
isInIncidentPage
()
||
isInDesignPage
()
isInIssuePage
()
||
isInIncidentPage
()
||
isInDesignPage
()
?
IssuableType
.
Issue
?
IssuableType
.
Issue
:
IssuableType
.
MergeRequest
,
:
IssuableType
.
MergeRequest
,
issuableId
:
id
,
multipleAssignees
:
!
el
.
dataset
.
maxAssignees
,
multipleAssignees
:
!
el
.
dataset
.
maxAssignees
,
},
},
scopedSlots
:
{
scopedSlots
:
{
...
...
app/assets/javascripts/sidebar/queries/issuable_assignees.subscription.graphql
0 → 100644
View file @
06413304
#import "~/graphql_shared/fragments/user.fragment.graphql"
subscription
issuableAssigneesUpdated
(
$issuableId
:
IssuableID
!)
{
issuableAssigneesUpdated
(
issuableId
:
$issuableId
)
{
...
on
Issue
{
assignees
{
nodes
{
...
User
status
{
availability
}
}
}
}
}
}
app/helpers/issuables_helper.rb
View file @
06413304
...
@@ -386,6 +386,7 @@ module IssuablesHelper
...
@@ -386,6 +386,7 @@ module IssuablesHelper
rootPath:
root_path
,
rootPath:
root_path
,
fullPath:
issuable
[
:project_full_path
],
fullPath:
issuable
[
:project_full_path
],
iid:
issuable
[
:iid
],
iid:
issuable
[
:iid
],
id:
issuable
[
:id
],
severity:
issuable
[
:severity
],
severity:
issuable
[
:severity
],
timeTrackingLimitToHours:
Gitlab
::
CurrentSettings
.
time_tracking_limit_to_hours
,
timeTrackingLimitToHours:
Gitlab
::
CurrentSettings
.
time_tracking_limit_to_hours
,
createNoteEmail:
issuable
[
:create_note_email
],
createNoteEmail:
issuable
[
:create_note_email
],
...
...
spec/features/action_cable_logging_spec.rb
View file @
06413304
...
@@ -22,11 +22,7 @@ RSpec.describe 'ActionCable logging', :js do
...
@@ -22,11 +22,7 @@ RSpec.describe 'ActionCable logging', :js do
subscription_data
=
a_hash_including
(
subscription_data
=
a_hash_including
(
remote_ip:
'127.0.0.1'
,
remote_ip:
'127.0.0.1'
,
user_id:
user
.
id
,
user_id:
user
.
id
,
username:
user
.
username
,
username:
user
.
username
params:
a_hash_including
(
project_path:
project
.
full_path
,
iid:
issue
.
iid
.
to_s
)
)
)
expect
(
ActiveSupport
::
Notifications
).
to
receive
(
:instrument
).
with
(
'subscribe.action_cable'
,
subscription_data
)
expect
(
ActiveSupport
::
Notifications
).
to
receive
(
:instrument
).
with
(
'subscribe.action_cable'
,
subscription_data
)
...
...
spec/frontend/actioncable_link_spec.js
0 → 100644
View file @
06413304
import
{
print
}
from
'
graphql
'
;
import
gql
from
'
graphql-tag
'
;
import
cable
from
'
~/actioncable_consumer
'
;
import
ActionCableLink
from
'
~/actioncable_link
'
;
// Mock uuids module for determinism
jest
.
mock
(
'
~/diffs/utils/uuids
'
,
()
=>
({
uuids
:
()
=>
[
'
testuuid
'
],
}));
const
TEST_OPERATION
=
{
query
:
gql
`
query foo {
project {
id
}
}
`
,
operationName
:
'
foo
'
,
variables
:
[],
};
/**
* Create an observer that passes calls to the given spy.
*
* This helps us assert which calls were made in what order.
*/
const
createSpyObserver
=
(
spy
)
=>
({
next
:
(...
args
)
=>
spy
(
'
next
'
,
...
args
),
error
:
(...
args
)
=>
spy
(
'
error
'
,
...
args
),
complete
:
(...
args
)
=>
spy
(
'
complete
'
,
...
args
),
});
const
notify
=
(...
notifications
)
=>
{
notifications
.
forEach
((
data
)
=>
cable
.
subscriptions
.
notifyAll
(
'
received
'
,
data
));
};
const
getSubscriptionCount
=
()
=>
cable
.
subscriptions
.
subscriptions
.
length
;
describe
(
'
~/actioncable_link
'
,
()
=>
{
let
cableLink
;
beforeEach
(()
=>
{
jest
.
spyOn
(
cable
.
subscriptions
,
'
create
'
);
cableLink
=
new
ActionCableLink
();
});
describe
(
'
request
'
,
()
=>
{
let
subscription
;
let
spy
;
beforeEach
(()
=>
{
spy
=
jest
.
fn
();
subscription
=
cableLink
.
request
(
TEST_OPERATION
).
subscribe
(
createSpyObserver
(
spy
));
});
afterEach
(()
=>
{
subscription
.
unsubscribe
();
});
it
(
'
creates a subscription
'
,
()
=>
{
expect
(
getSubscriptionCount
()).
toBe
(
1
);
expect
(
cable
.
subscriptions
.
create
).
toHaveBeenCalledWith
(
{
channel
:
'
GraphqlChannel
'
,
nonce
:
'
testuuid
'
,
...
TEST_OPERATION
,
query
:
print
(
TEST_OPERATION
.
query
),
},
{
received
:
expect
.
any
(
Function
)
},
);
});
it
(
'
when "unsubscribe", unsubscribes underlying cable subscription
'
,
()
=>
{
subscription
.
unsubscribe
();
expect
(
getSubscriptionCount
()).
toBe
(
0
);
});
it
(
'
when receives data, triggers observer until no ".more"
'
,
()
=>
{
notify
(
{
result
:
'
test result
'
,
more
:
true
},
{
result
:
'
test result 2
'
,
more
:
true
},
{
result
:
'
test result 3
'
},
{
result
:
'
test result 4
'
},
);
expect
(
spy
.
mock
.
calls
).
toEqual
([
[
'
next
'
,
'
test result
'
],
[
'
next
'
,
'
test result 2
'
],
[
'
next
'
,
'
test result 3
'
],
[
'
complete
'
],
]);
});
it
(
'
when receives errors, triggers observer
'
,
()
=>
{
notify
(
{
result
:
'
test result
'
,
more
:
true
},
{
result
:
'
test result 2
'
,
errors
:
[
'
boom!
'
],
more
:
true
},
{
result
:
'
test result 3
'
},
);
expect
(
spy
.
mock
.
calls
).
toEqual
([
[
'
next
'
,
'
test result
'
],
[
'
error
'
,
[
'
boom!
'
]],
]);
});
});
});
spec/frontend/sidebar/assignees_realtime_spec.js
View file @
06413304
import
ActionCable
from
'
@rails/actioncable
'
;
import
{
shallowMount
,
createLocalVue
}
from
'
@vue/test-utils
'
;
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
createMockApollo
from
'
helpers/mock_apollo_helper
'
;
import
AssigneesRealtime
from
'
~/sidebar/components/assignees/assignees_realtime.vue
'
;
import
AssigneesRealtime
from
'
~/sidebar/components/assignees/assignees_realtime.vue
'
;
import
{
assigneesQueries
}
from
'
~/sidebar/constants
'
;
import
issuableAssigneesSubscription
from
'
~/sidebar/queries/issuable_assignees.subscription.graphql
'
;
import
SidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
SidebarMediator
from
'
~/sidebar/sidebar_mediator
'
;
import
Mock
from
'
./mock_data
'
;
import
getIssueParticipantsQuery
from
'
~/vue_shared/components/sidebar/queries/get_issue_participants.query.graphql
'
;
import
Mock
,
{
issuableQueryResponse
,
subscriptionNullResponse
}
from
'
./mock_data
'
;
jest
.
mock
(
'
@rails/actioncable
'
,
()
=>
{
const
localVue
=
createLocalVue
();
const
mockConsumer
=
{
localVue
.
use
(
VueApollo
);
subscriptions
:
{
create
:
jest
.
fn
().
mockReturnValue
({
unsubscribe
:
jest
.
fn
()
})
},
};
return
{
createConsumer
:
jest
.
fn
().
mockReturnValue
(
mockConsumer
),
};
});
describe
(
'
Assignees Realtime
'
,
()
=>
{
describe
(
'
Assignees Realtime
'
,
()
=>
{
let
wrapper
;
let
wrapper
;
let
mediator
;
let
mediator
;
let
fakeApollo
;
const
issuableQueryHandler
=
jest
.
fn
().
mockResolvedValue
(
issuableQueryResponse
);
const
subscriptionInitialHandler
=
jest
.
fn
().
mockResolvedValue
(
subscriptionNullResponse
);
const
createComponent
=
(
issuableType
=
'
issue
'
)
=>
{
const
createComponent
=
({
issuableType
=
'
issue
'
,
issuableId
=
1
,
subscriptionHandler
=
subscriptionInitialHandler
,
}
=
{})
=>
{
fakeApollo
=
createMockApollo
([
[
getIssueParticipantsQuery
,
issuableQueryHandler
],
[
issuableAssigneesSubscription
,
subscriptionHandler
],
]);
wrapper
=
shallowMount
(
AssigneesRealtime
,
{
wrapper
=
shallowMount
(
AssigneesRealtime
,
{
propsData
:
{
propsData
:
{
issuableType
,
issuableId
,
queryVariables
:
{
issuableIid
:
'
1
'
,
issuableIid
:
'
1
'
,
mediator
,
projectPath
:
'
path/to/project
'
,
projectPath
:
'
path/to/project
'
,
issuableType
,
},
mocks
:
{
$apollo
:
{
query
:
assigneesQueries
[
issuableType
].
query
,
queries
:
{
workspace
:
{
refetch
:
jest
.
fn
(),
},
},
},
},
mediator
,
},
},
apolloProvider
:
fakeApollo
,
localVue
,
});
});
};
};
...
@@ -45,59 +48,24 @@ describe('Assignees Realtime', () => {
...
@@ -45,59 +48,24 @@ describe('Assignees Realtime', () => {
afterEach
(()
=>
{
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
.
destroy
();
wrapper
=
null
;
fakeApollo
=
null
;
SidebarMediator
.
singleton
=
null
;
SidebarMediator
.
singleton
=
null
;
});
});
describe
(
'
when handleFetchResult is called from smart query
'
,
()
=>
{
it
(
'
calls the query with correct variables
'
,
()
=>
{
it
(
'
sets assignees to the store
'
,
()
=>
{
const
data
=
{
workspace
:
{
issuable
:
{
assignees
:
{
nodes
:
[{
id
:
'
gid://gitlab/Environments/123
'
,
avatarUrl
:
'
url
'
}],
},
},
},
};
const
expected
=
[{
id
:
123
,
avatar_url
:
'
url
'
,
avatarUrl
:
'
url
'
}];
createComponent
();
wrapper
.
vm
.
handleFetchResult
({
data
});
expect
(
mediator
.
store
.
assignees
).
toEqual
(
expected
);
});
});
describe
(
'
when mounted
'
,
()
=>
{
it
(
'
calls create subscription
'
,
()
=>
{
const
cable
=
ActionCable
.
createConsumer
();
createComponent
();
createComponent
();
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
issuableQueryHandler
).
toHaveBeenCalledWith
({
expect
(
cable
.
subscriptions
.
create
).
toHaveBeenCalledTimes
(
1
);
issuableIid
:
'
1
'
,
expect
(
cable
.
subscriptions
.
create
).
toHaveBeenCalledWith
(
projectPath
:
'
path/to/project
'
,
{
channel
:
'
IssuesChannel
'
,
iid
:
wrapper
.
props
(
'
issuableIid
'
),
project_path
:
wrapper
.
props
(
'
projectPath
'
),
},
{
received
:
wrapper
.
vm
.
received
},
);
});
});
});
});
});
describe
(
'
when subscription is recieved
'
,
()
=>
{
it
(
'
calls the subscription with correct variable for issue
'
,
()
=>
{
it
(
'
refetches the GraphQL project query
'
,
()
=>
{
createComponent
();
createComponent
();
wrapper
.
vm
.
received
({
event
:
'
updated
'
});
expect
(
subscriptionInitialHandler
).
toHaveBeenCalledWith
({
issuableId
:
'
gid://gitlab/Issue/1
'
,
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
wrapper
.
vm
.
$apollo
.
queries
.
workspace
.
refetch
).
toHaveBeenCalledTimes
(
1
);
});
});
});
});
});
});
});
spec/frontend/sidebar/components/assignees/sidebar_assignees_widget_spec.js
View file @
06413304
...
@@ -487,6 +487,9 @@ describe('Sidebar assignees widget', () => {
...
@@ -487,6 +487,9 @@ describe('Sidebar assignees widget', () => {
it
(
'
when realtime feature flag is enabled
'
,
async
()
=>
{
it
(
'
when realtime feature flag is enabled
'
,
async
()
=>
{
createComponent
({
createComponent
({
props
:
{
issuableId
:
1
,
},
provide
:
{
provide
:
{
glFeatures
:
{
glFeatures
:
{
realTimeIssueSidebar
:
true
,
realTimeIssueSidebar
:
true
,
...
...
spec/frontend/sidebar/mock_data.js
View file @
06413304
...
@@ -401,4 +401,10 @@ export const updateIssueAssigneesMutationResponse = {
...
@@ -401,4 +401,10 @@ export const updateIssueAssigneesMutationResponse = {
},
},
};
};
export
const
subscriptionNullResponse
=
{
data
:
{
issuableAssigneesUpdated
:
null
,
},
};
export
default
mockData
;
export
default
mockData
;
spec/frontend/sidebar/sidebar_assignees_spec.js
View file @
06413304
...
@@ -17,6 +17,7 @@ describe('sidebar assignees', () => {
...
@@ -17,6 +17,7 @@ describe('sidebar assignees', () => {
wrapper
=
shallowMount
(
SidebarAssignees
,
{
wrapper
=
shallowMount
(
SidebarAssignees
,
{
propsData
:
{
propsData
:
{
issuableIid
:
'
1
'
,
issuableIid
:
'
1
'
,
issuableId
:
1
,
mediator
,
mediator
,
field
:
''
,
field
:
''
,
projectPath
:
'
projectPath
'
,
projectPath
:
'
projectPath
'
,
...
...
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