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
86665d7d
Commit
86665d7d
authored
Sep 29, 2020
by
Martin Wortschack
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Switch to graphql in projects dropdown
- This MR switches to the grapqhl API in the project dropdown selector.
parent
a58d9a88
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
431 additions
and
166 deletions
+431
-166
ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue
...javascripts/analytics/cycle_analytics/components/base.vue
+1
-0
ee/app/assets/javascripts/analytics/productivity_analytics/components/filter_dropdowns.vue
...cs/productivity_analytics/components/filter_dropdowns.vue
+6
-12
ee/app/assets/javascripts/analytics/productivity_analytics/index.js
...ets/javascripts/analytics/productivity_analytics/index.js
+18
-3
ee/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
.../analytics/shared/components/projects_dropdown_filter.vue
+45
-4
ee/app/assets/javascripts/analytics/shared/graphql/projects.query.graphql
...vascripts/analytics/shared/graphql/projects.query.graphql
+22
-0
ee/app/assets/javascripts/analytics/shared/utils.js
ee/app/assets/javascripts/analytics/shared/utils.js
+3
-3
ee/lib/analytics/productivity_analytics_request_params.rb
ee/lib/analytics/productivity_analytics_request_params.rb
+1
-0
ee/spec/frontend/analytics/productivity_analytics/components/filter_dropdowns_spec.js
...roductivity_analytics/components/filter_dropdowns_spec.js
+4
-2
ee/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
...lytics/shared/components/projects_dropdown_filter_spec.js
+329
-141
ee/spec/frontend/analytics/shared/utils_spec.js
ee/spec/frontend/analytics/shared/utils_spec.js
+2
-1
No files found.
ee/app/assets/javascripts/analytics/cycle_analytics/components/base.vue
View file @
86665d7d
...
...
@@ -210,6 +210,7 @@ export default {
:key=
"currentGroup.id"
class=
"js-projects-dropdown-filter project-select"
:group-id=
"currentGroup.id"
:group-namespace=
"currentGroupPath"
:query-params=
"projectsQueryParams"
:multi-select=
"$options.multiProjectSelect"
:default-projects=
"selectedProjects"
...
...
ee/app/assets/javascripts/analytics/productivity_analytics/components/filter_dropdowns.vue
View file @
86665d7d
...
...
@@ -4,7 +4,6 @@ import GroupsDropdownFilter from '../../shared/components/groups_dropdown_filter
import
glFeatureFlagsMixin
from
'
~/vue_shared/mixins/gl_feature_flags_mixin
'
;
import
ProjectsDropdownFilter
from
'
../../shared/components/projects_dropdown_filter.vue
'
;
import
{
accessLevelReporter
,
projectsPerPage
}
from
'
../constants
'
;
import
{
SIMILARITY_ORDER
,
LAST_ACTIVITY_AT
}
from
'
../../shared/constants
'
;
export
default
{
components
:
{
...
...
@@ -44,10 +43,8 @@ export default {
},
projectsQueryParams
()
{
return
{
per_page
:
projectsPerPage
,
with_shared
:
false
,
// exclude forks
order_by
:
this
.
glFeatures
.
analyticsSimilaritySearch
?
SIMILARITY_ORDER
:
LAST_ACTIVITY_AT
,
include_subgroups
:
true
,
first
:
projectsPerPage
,
includeSubgroups
:
true
,
};
},
},
...
...
@@ -59,13 +56,8 @@ export default {
this
.
$emit
(
'
groupSelected
'
,
{
groupId
:
id
,
groupNamespace
:
full_path
});
},
onProjectsSelected
(
selectedProjects
)
{
let
projectNamespace
=
null
;
let
projectId
=
null
;
if
(
selectedProjects
.
length
)
{
projectNamespace
=
selectedProjects
[
0
].
path_with_namespace
;
projectId
=
selectedProjects
[
0
].
id
;
}
const
projectNamespace
=
selectedProjects
[
0
]?.
fullPath
||
null
;
const
projectId
=
selectedProjects
[
0
]?.
id
||
null
;
this
.
setProjectPath
(
projectNamespace
);
this
.
$emit
(
'
projectSelected
'
,
{
...
...
@@ -98,6 +90,8 @@ export default {
:default-projects=
"projects"
:query-params=
"projectsQueryParams"
:group-id=
"groupId"
:group-namespace=
"groupNamespace"
:use-graphql=
"true"
@
selected=
"onProjectsSelected"
/>
</div>
...
...
ee/app/assets/javascripts/analytics/productivity_analytics/index.js
View file @
86665d7d
import
Vue
from
'
vue
'
;
import
VueApollo
from
'
vue-apollo
'
;
import
{
mapState
,
mapActions
}
from
'
vuex
'
;
import
store
from
'
./store
'
;
import
FilterDropdowns
from
'
./components/filter_dropdowns.vue
'
;
import
DateRange
from
'
../shared/components/daterange.vue
'
;
import
ProductivityAnalyticsApp
from
'
./components/app.vue
'
;
import
FilteredSearchProductivityAnalytics
from
'
./filtered_search_productivity_analytics
'
;
import
createDefaultClient
from
'
~/lib/graphql
'
;
import
{
parseBoolean
}
from
'
~/lib/utils/common_utils
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
getLabelsEndpoint
,
getMilestonesEndpoint
}
from
'
./utils
'
;
import
{
buildGroupFromDataset
,
buildProjectFromDataset
}
from
'
../shared/utils
'
;
Vue
.
use
(
VueApollo
);
const
apolloProvider
=
new
VueApollo
({
defaultClient
:
createDefaultClient
(),
});
export
default
()
=>
{
const
container
=
document
.
getElementById
(
'
js-productivity-analytics
'
);
const
groupProjectSelectContainer
=
container
.
querySelector
(
'
.js-group-project-select-container
'
);
...
...
@@ -65,6 +74,7 @@ export default () => {
// eslint-disable-next-line no-new
new
Vue
({
el
:
groupProjectSelectContainer
,
apolloProvider
,
store
,
created
()
{
// let's not fetch any data by default since we might not have a valid group yet
...
...
@@ -76,8 +86,8 @@ export default () => {
this
.
initFilteredSearch
({
groupNamespace
:
group
.
full_path
,
groupId
:
group
.
id
,
projectNamespace
:
project
?
project
.
path_with_namespace
:
null
,
projectId
:
project
?
project
.
id
:
null
,
projectNamespace
:
project
?.
path_with_namespace
||
null
,
projectId
:
container
.
dataset
.
projectId
||
null
,
});
// let's fetch data now since we do have a valid group
...
...
@@ -93,7 +103,12 @@ export default () => {
this
.
initFilteredSearch
({
groupNamespace
,
groupId
});
},
onProjectSelected
({
groupNamespace
,
groupId
,
projectNamespace
,
projectId
})
{
this
.
initFilteredSearch
({
groupNamespace
,
groupId
,
projectNamespace
,
projectId
});
this
.
initFilteredSearch
({
groupNamespace
,
groupId
,
projectNamespace
,
projectId
:
getIdFromGraphQLId
(
projectId
),
});
},
initFilteredSearch
({
groupNamespace
,
groupId
,
projectNamespace
=
''
,
projectId
=
null
})
{
// let's unbind attached event handlers first and reset the template
...
...
ee/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
View file @
86665d7d
...
...
@@ -11,8 +11,10 @@ import {
}
from
'
@gitlab/ui
'
;
import
{
n__
,
s__
,
__
}
from
'
~/locale
'
;
import
Api
from
'
~/api
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
{
DATA_REFETCH_DELAY
}
from
'
../constants
'
;
import
{
filterBySearchTerm
}
from
'
../utils
'
;
import
getProjects
from
'
../graphql/projects.query.graphql
'
;
export
default
{
name
:
'
ProjectsDropdownFilter
'
,
...
...
@@ -30,6 +32,10 @@ export default {
type
:
Number
,
required
:
true
,
},
groupNamespace
:
{
type
:
String
,
required
:
true
,
},
multiSelect
:
{
type
:
Boolean
,
required
:
false
,
...
...
@@ -50,6 +56,11 @@ export default {
required
:
false
,
default
:
()
=>
[],
},
useGraphql
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
...
...
@@ -121,6 +132,31 @@ export default {
},
fetchData
()
{
this
.
loading
=
true
;
if
(
this
.
useGraphql
)
{
return
this
.
$apollo
.
query
({
query
:
getProjects
,
variables
:
{
groupFullPath
:
this
.
groupNamespace
,
search
:
this
.
searchTerm
,
...
this
.
queryParams
,
},
})
.
then
(
response
=>
{
const
{
data
:
{
group
:
{
projects
:
{
nodes
},
},
},
}
=
response
;
this
.
loading
=
false
;
this
.
projects
=
nodes
;
});
}
return
Api
.
groupProjects
(
this
.
groupId
,
this
.
searchTerm
,
this
.
queryParams
,
projects
=>
{
this
.
projects
=
projects
;
this
.
loading
=
false
;
...
...
@@ -129,6 +165,11 @@ export default {
isProjectSelected
(
id
)
{
return
this
.
selectedProjects
?
this
.
selectedProjectIds
.
includes
(
id
)
:
false
;
},
getEntityId
(
project
)
{
if
(
this
.
useGraphql
)
return
getIdFromGraphQLId
(
project
.
id
);
return
project
?.
id
||
null
;
},
},
};
</
script
>
...
...
@@ -143,8 +184,8 @@ export default {
<div
class=
"gl-display-flex gl-flex-fill-1"
>
<gl-avatar
v-if=
"isOnlyOneProjectSelected"
:src=
"selectedProjects[0].avatar_url"
:entity-id=
"
selectedProjects[0].id
"
:src=
"
useGraphql ? selectedProjects[0].avatarUrl :
selectedProjects[0].avatar_url"
:entity-id=
"
getEntityId(selectedProjects[0])
"
:entity-name=
"selectedProjects[0].name"
:size=
"16"
shape=
"rect"
...
...
@@ -170,9 +211,9 @@ export default {
class=
"gl-mr-2 vertical-align-middle"
:alt=
"project.name"
:size=
"16"
:entity-id=
"
project.id
"
:entity-id=
"
getEntityId(project)
"
:entity-name=
"project.name"
:src=
"project.avatar_url"
:src=
"
useGraphql ? project.avatarUrl :
project.avatar_url"
shape=
"rect"
/>
{{ project.name }}
...
...
ee/app/assets/javascripts/analytics/shared/graphql/projects.query.graphql
0 → 100644
View file @
86665d7d
query
getGroupProjects
(
$groupFullPath
:
ID
!
$search
:
String
!
$first
:
Int
!
$includeSubgroups
:
Boolean
=
false
)
{
group
(
fullPath
:
$groupFullPath
)
{
projects
(
search
:
$search
first
:
$first
includeSubgroups
:
$includeSubgroups
sort
:
SIMILARITY
)
{
nodes
{
id
name
avatarUrl
fullPath
}
}
}
}
ee/app/assets/javascripts/analytics/shared/utils.js
View file @
86665d7d
...
...
@@ -39,11 +39,11 @@ export const buildGroupFromDataset = dataset => {
* @returns {Object} - A project object
*/
export
const
buildProjectFromDataset
=
dataset
=>
{
const
{
project
I
d
,
projectName
,
projectPathWithNamespace
,
projectAvatarUrl
}
=
dataset
;
const
{
project
Gi
d
,
projectName
,
projectPathWithNamespace
,
projectAvatarUrl
}
=
dataset
;
if
(
project
I
d
)
{
if
(
project
Gi
d
)
{
return
{
id
:
Number
(
projectId
)
,
id
:
projectGid
,
name
:
projectName
,
path_with_namespace
:
projectPathWithNamespace
,
avatar_url
:
projectAvatarUrl
,
...
...
ee/lib/analytics/productivity_analytics_request_params.rb
View file @
86665d7d
...
...
@@ -66,6 +66,7 @@ module Analytics
def
project_data_attributes
{
id:
project
.
id
,
gid:
project
.
to_gid
.
to_s
,
name:
project
.
name
,
path_with_namespace:
project
.
path_with_namespace
,
avatar_url:
project
.
avatar_url
...
...
ee/spec/frontend/analytics/productivity_analytics/components/filter_dropdowns_spec.js
View file @
86665d7d
...
...
@@ -21,7 +21,7 @@ describe('FilterDropdowns component', () => {
const
groupId
=
1
;
const
groupNamespace
=
'
gitlab-org
'
;
const
projectPath
=
'
gitlab-org/gitlab-test
'
;
const
projectId
=
10
;
const
projectId
=
'
gid://gitlab/Project/1
'
;
beforeEach
(()
=>
{
const
{
...
...
@@ -41,6 +41,7 @@ describe('FilterDropdowns component', () => {
...
modules
,
},
});
wrapper
=
shallowMount
(
FilterDropdowns
,
{
localVue
,
store
:
mockStore
,
...
...
@@ -71,6 +72,7 @@ describe('FilterDropdowns component', () => {
describe
(
'
with a group selected
'
,
()
=>
{
beforeEach
(()
=>
{
wrapper
.
vm
.
groupId
=
groupId
;
mockStore
.
state
.
filters
.
groupNamespace
=
groupNamespace
;
});
it
(
'
renders the projects dropdown
'
,
()
=>
{
...
...
@@ -107,7 +109,7 @@ describe('FilterDropdowns component', () => {
describe
(
'
when the list of selected projects is not empty
'
,
()
=>
{
beforeEach
(()
=>
{
mockStore
.
state
.
filters
.
groupNamespace
=
groupNamespace
;
wrapper
.
vm
.
onProjectsSelected
([{
id
:
projectId
,
path_with_namespace
:
`
${
projectPath
}
`
}]);
wrapper
.
vm
.
onProjectsSelected
([{
id
:
projectId
,
fullPath
:
`
${
projectPath
}
`
}]);
});
it
(
'
invokes setProjectPath action
'
,
()
=>
{
...
...
ee/spec/frontend/analytics/shared/components/projects_dropdown_filter_spec.js
View file @
86665d7d
import
{
mount
}
from
'
@vue/test-utils
'
;
import
ProjectsDropdownFilter
from
'
ee/analytics/shared/components/projects_dropdown_filter.vue
'
;
import
getProjects
from
'
ee/analytics/shared/graphql/projects.query.graphql
'
;
import
{
GlDropdown
,
GlDropdownItem
}
from
'
@gitlab/ui
'
;
import
{
LAST_ACTIVITY_AT
}
from
'
ee/analytics/shared/constants
'
;
import
{
TEST_HOST
}
from
'
helpers/test_constants
'
;
...
...
@@ -9,6 +10,27 @@ jest.mock('~/api', () => ({
groupProjects
:
jest
.
fn
(),
}));
const
mockGraphqlProjects
=
[
{
id
:
'
gid://gitlab/Project/1
'
,
name
:
'
Gitlab Test
'
,
fullPath
:
'
gitlab-org/gitlab-test
'
,
avatarUrl
:
`
${
TEST_HOST
}
/images/home/nasa.svg`
,
},
{
id
:
'
gid://gitlab/Project/2
'
,
name
:
'
Gitlab Shell
'
,
fullPath
:
'
gitlab-org/gitlab-shell
'
,
avatarUrl
:
null
,
},
{
id
:
'
gid://gitlab/Project/3
'
,
name
:
'
Foo
'
,
fullPath
:
'
gitlab-org/foo
'
,
avatarUrl
:
null
,
},
];
const
projects
=
[
{
id
:
1
,
...
...
@@ -27,13 +49,26 @@ const projects = [
},
];
const
defaultMocks
=
{
$apollo
:
{
query
:
jest
.
fn
().
mockResolvedValue
({
data
:
{
group
:
{
projects
:
{
nodes
:
mockGraphqlProjects
}
}
},
}),
},
};
let
spyQuery
;
describe
(
'
ProjectsDropdownFilter component
'
,
()
=>
{
let
wrapper
;
const
createComponent
=
(
props
=
{})
=>
{
spyQuery
=
defaultMocks
.
$apollo
.
query
;
wrapper
=
mount
(
ProjectsDropdownFilter
,
{
mocks
:
{
...
defaultMocks
},
propsData
:
{
groupId
:
1
,
groupNamespace
:
'
gitlab-org
'
,
...
props
,
},
});
...
...
@@ -43,12 +78,6 @@ describe('ProjectsDropdownFilter component', () => {
wrapper
.
destroy
();
});
beforeEach
(()
=>
{
Api
.
groupProjects
.
mockImplementation
((
groupId
,
term
,
options
,
callback
)
=>
{
callback
(
projects
);
});
});
const
findDropdown
=
()
=>
wrapper
.
find
(
GlDropdown
);
const
findDropdownItems
=
()
=>
...
...
@@ -60,203 +89,362 @@ describe('ProjectsDropdownFilter component', () => {
const
findDropdownButton
=
()
=>
findDropdown
().
find
(
'
.dropdown-toggle
'
);
const
findDropdownButtonAvatar
=
()
=>
findDropdown
().
find
(
'
.gl-avatar
'
);
const
findDropdownButtonAvatarAtIndex
=
index
=>
findDropdownAtIndex
(
index
).
find
(
'
img.gl-avatar
'
);
const
findDropdownButtonIdentIconAtIndex
=
index
=>
findDropdownAtIndex
(
index
).
find
(
'
div.gl-avatar-identicon
'
);
const
selectDropdownItemAtIndex
=
index
=>
findDropdownAtIndex
(
index
)
.
find
(
'
button
'
)
.
trigger
(
'
click
'
);
describe
(
'
queryParams are applied when fetching data
'
,
()
=>
{
describe
(
'
when using the REST API
'
,
()
=>
{
describe
(
'
queryParams are applied when fetching data
'
,
()
=>
{
beforeEach
(()
=>
{
Api
.
groupProjects
.
mockImplementation
((
groupId
,
term
,
options
,
callback
)
=>
{
callback
(
projects
);
});
createComponent
({
queryParams
:
{
per_page
:
50
,
with_shared
:
false
,
order_by
:
LAST_ACTIVITY_AT
,
},
});
});
it
(
'
applies the correct queryParams when making an api call
'
,
()
=>
{
expect
(
Api
.
groupProjects
).
toHaveBeenCalledWith
(
expect
.
any
(
Number
),
expect
.
any
(
String
),
expect
.
objectContaining
({
per_page
:
50
,
with_shared
:
false
,
order_by
:
LAST_ACTIVITY_AT
}),
expect
.
any
(
Function
),
);
});
});
});
describe
(
'
when using the GraphQL API
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
useGraphql
:
true
,
queryParams
:
{
per_page
:
50
,
with_shared
:
false
,
order_by
:
LAST_ACTIVITY_AT
,
first
:
50
,
includeSubgroups
:
true
,
},
});
});
it
(
'
applies the correct queryParams when making an api call
'
,
()
=>
{
expect
(
Api
.
groupProjects
).
toHaveBeenCalledWith
(
expect
.
any
(
Number
),
expect
.
any
(
String
),
expect
.
objectContaining
({
per_page
:
50
,
with_shared
:
false
,
order_by
:
LAST_ACTIVITY_AT
}),
expect
.
any
(
Function
),
);
it
(
'
applies the correct queryParams when making an api call
'
,
async
()
=>
{
wrapper
.
setData
({
searchTerm
:
'
gitlab
'
});
expect
(
spyQuery
).
toHaveBeenCalledTimes
(
1
);
await
wrapper
.
vm
.
$nextTick
(()
=>
{
expect
(
spyQuery
).
toHaveBeenCalledWith
({
query
:
getProjects
,
variables
:
{
search
:
'
gitlab
'
,
groupFullPath
:
wrapper
.
vm
.
groupNamespace
,
first
:
50
,
includeSubgroups
:
true
,
},
});
});
});
});
describe
(
'
when passed a an array of defaultProject as prop
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
defaultProjects
:
[
projects
[
0
]],
describe
(
'
when using the RESTP API
'
,
()
=>
{
beforeEach
(()
=>
{
Api
.
groupProjects
.
mockImplementation
((
groupId
,
term
,
options
,
callback
)
=>
{
callback
(
projects
);
});
createComponent
({
defaultProjects
:
[
projects
[
0
]],
});
});
});
it
(
"
displays the defaultProject's name
"
,
()
=>
{
expect
(
findDropdownButton
().
text
()).
toContain
(
projects
[
0
].
name
);
});
it
(
"
displays the defaultProject's name
"
,
()
=>
{
expect
(
findDropdownButton
().
text
()).
toContain
(
projects
[
0
].
name
);
});
it
(
"
renders the defaultProject's avatar
"
,
()
=>
{
expect
(
findDropdownButtonAvatar
().
exists
()).
toBe
(
true
);
});
it
(
"
renders the defaultProject's avatar
"
,
()
=>
{
expect
(
findDropdownButtonAvatar
().
exists
()).
toBe
(
true
);
});
it
(
'
marks the defaultProject as selected
'
,
()
=>
{
expect
(
findDropdownAtIndex
(
0
).
props
(
'
isChecked
'
)).
toBe
(
true
);
it
(
'
marks the defaultProject as selected
'
,
()
=>
{
expect
(
findDropdownAtIndex
(
0
).
props
(
'
isChecked
'
)).
toBe
(
true
);
});
});
});
describe
(
'
when multiSelect is false
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
multiSelect
:
false
});
});
describe
(
'
when using the GraphQL API
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
useGraphql
:
true
,
defaultProjects
:
[
mockGraphqlProjects
[
0
]],
});
});
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
it
(
"
displays the defaultProject's name
"
,
()
=>
{
expect
(
findDropdownButton
().
text
()).
toContain
(
mockGraphqlProjects
[
0
].
name
);
});
it
(
'
renders an avatar when the project has an avatar_url
'
,
()
=>
{
expect
(
findDropdownAtIndex
(
0
)
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
true
);
expect
(
findDropdownAtIndex
(
0
)
.
find
(
'
div.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
false
);
it
(
"
renders the defaultProject's avatar
"
,
()
=>
{
expect
(
findDropdownButtonAvatar
().
exists
()).
toBe
(
true
);
});
it
(
"
renders an identicon when the project doesn't have an avatar_url
"
,
()
=>
{
expect
(
findDropdownAtIndex
(
1
)
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
false
);
expect
(
findDropdownAtIndex
(
1
)
.
find
(
'
div.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
true
);
it
(
'
marks the defaultProject as selected
'
,
()
=>
{
expect
(
findDropdownAtIndex
(
0
).
props
(
'
isChecked
'
)).
toBe
(
true
);
});
});
});
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should emit the "selected" event with the selected project
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
describe
(
'
when multiSelect is false
'
,
()
=>
{
describe
(
'
when using the RESTP API
'
,
()
=>
{
beforeEach
(()
=>
{
Api
.
groupProjects
.
mockImplementation
((
groupId
,
term
,
options
,
callback
)
=>
{
callback
(
projects
);
});
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]]]
);
createComponent
({
multiSelect
:
false
}
);
});
it
(
'
should change selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
1
);
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
});
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
1
]]]]);
it
(
'
renders an avatar when the project has an avatar_url
'
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
0
).
exists
()).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
it
(
"
renders an identicon when the project doesn't have an avatar_url
"
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
1
).
exists
()).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
it
(
'
selection should be emptied when a project is deselected
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
// Select the item
selectDropdownItemAtIndex
(
0
);
// deselect it
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should emit the "selected" event with the selected project
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]]]);
});
it
(
'
should change selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
1
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
1
]]]]);
});
it
(
'
selection should be emptied when a project is deselected
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
// Select the item
selectDropdownItemAtIndex
(
0
);
// deselect it
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]],
[[]]]);
});
it
(
'
renders an avatar in the dropdown button when the project has an avatar_url
'
,
async
()
=>
{
selectDropdownItemAtIndex
(
0
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
});
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]],
[[]]]);
it
(
"
renders an identicon in the dropdown button when the project doesn't have an avatar_url
"
,
async
()
=>
{
selectDropdownItemAtIndex
(
1
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
});
});
describe
(
'
when using the GraphQl API
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
multiSelect
:
false
,
useGraphql
:
true
});
});
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
});
it
(
'
renders an avatar in the dropdown button when the project has an avatar_url
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
true
);
expect
(
findDropdownButton
()
.
find
(
'
.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
false
);
it
(
'
renders an avatar when the project has an avatarUrl
'
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
0
).
exists
()).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
it
(
"
renders an identicon when the project doesn't have an avatarUrl
"
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
1
).
exists
()).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
it
(
"
renders an identicon in the dropdown button when the project doesn't have an avatar_url
"
,
()
=>
{
selectDropdownItemAtIndex
(
1
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
false
);
expect
(
findDropdownButton
()
.
find
(
'
.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
true
);
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should emit the "selected" event with the selected project
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
mockGraphqlProjects
[
0
]]]]);
});
it
(
'
should change selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
1
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
mockGraphqlProjects
[
1
]]]]);
});
it
(
'
selection should be emptied when a project is deselected
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
// Select the item
selectDropdownItemAtIndex
(
0
);
// deselect it
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
mockGraphqlProjects
[
0
]]],
[[]]]);
});
it
(
'
renders an avatar in the dropdown button when the project has an avatarUrl
'
,
async
()
=>
{
selectDropdownItemAtIndex
(
0
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
});
it
(
"
renders an identicon in the dropdown button when the project doesn't have an avatarUrl
"
,
async
()
=>
{
selectDropdownItemAtIndex
(
1
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
()
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
});
});
});
describe
(
'
when multiSelect is true
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
multiSelect
:
true
});
});
describe
(
'
when using the RESTP API
'
,
()
=>
{
beforeEach
(()
=>
{
Api
.
groupProjects
.
mockImplementation
((
groupId
,
term
,
options
,
callback
)
=>
{
callback
(
projects
);
});
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
createComponent
({
multiSelect
:
true
});
});
it
(
'
renders an avatar when the project has an avatar_url
'
,
()
=>
{
expect
(
findDropdownAtIndex
(
0
)
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
true
);
expect
(
findDropdownAtIndex
(
0
)
.
find
(
'
div.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
false
);
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
});
it
(
'
renders an avatar when the project has an avatar_url
'
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
0
).
exists
()).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
it
(
"
renders an identicon when the project doesn't have an avatar_url
"
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
1
).
exists
()).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
it
(
"
renders an identicon when the project doesn't have an avatar_url
"
,
()
=>
{
expect
(
findDropdownAtIndex
(
1
)
.
find
(
'
img.gl-avatar
'
)
.
exists
(),
).
toBe
(
false
);
expect
(
findDropdownAtIndex
(
1
)
.
find
(
'
div.gl-avatar-identicon
'
)
.
exists
(),
).
toBe
(
true
);
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should add to selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([
[[
projects
[
0
]]],
[[
projects
[
0
],
projects
[
1
]]],
]);
});
it
(
'
should remove from selection when clicked again
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
0
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]],
[[]]]);
});
it
(
'
renders the correct placeholder text when multiple projects are selected
'
,
async
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
().
text
()).
toBe
(
'
2 projects selected
'
);
});
});
});
});
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should add to selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]],
[[
projects
[
0
],
projects
[
1
]]]]);
describe
(
'
when using the GraphQl API
'
,
()
=>
{
beforeEach
(()
=>
{
createComponent
({
multiSelect
:
true
,
useGraphql
:
true
});
});
it
(
'
should remove from selection when clicked again
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
0
);
describe
(
'
displays the correct information
'
,
()
=>
{
it
(
'
contains 3 items
'
,
()
=>
{
expect
(
findDropdownItems
()).
toHaveLength
(
3
);
});
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
projects
[
0
]]],
[[]]]);
it
(
'
renders an avatar when the project has an avatarUrl
'
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
0
).
exists
()).
toBe
(
true
);
expect
(
findDropdownButtonIdentIconAtIndex
(
0
).
exists
()).
toBe
(
false
);
});
it
(
"
renders an identicon when the project doesn't have an avatarUrl
"
,
()
=>
{
expect
(
findDropdownButtonAvatarAtIndex
(
1
).
exists
()).
toBe
(
false
);
expect
(
findDropdownButtonIdentIconAtIndex
(
1
).
exists
()).
toBe
(
true
);
});
});
it
(
'
renders the correct placeholder text when multiple projects are selected
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
describe
(
'
on project click
'
,
()
=>
{
it
(
'
should add to selection when new project is clicked
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([
[[
mockGraphqlProjects
[
0
]]],
[[
mockGraphqlProjects
[
0
],
mockGraphqlProjects
[
1
]]],
]);
});
it
(
'
should remove from selection when clicked again
'
,
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
0
);
expect
(
wrapper
.
emitted
().
selected
).
toEqual
([[[
mockGraphqlProjects
[
0
]]],
[[]]]);
});
it
(
'
renders the correct placeholder text when multiple projects are selected
'
,
async
()
=>
{
selectDropdownItemAtIndex
(
0
);
selectDropdownItemAtIndex
(
1
);
return
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
().
text
()).
toBe
(
'
2 projects selected
'
);
await
wrapper
.
vm
.
$nextTick
().
then
(()
=>
{
expect
(
findDropdownButton
().
text
()).
toBe
(
'
2 projects selected
'
);
});
});
});
});
...
...
ee/spec/frontend/analytics/shared/
components/
utils_spec.js
→
ee/spec/frontend/analytics/shared/utils_spec.js
View file @
86665d7d
...
...
@@ -22,6 +22,7 @@ const subGroupDataset = {
const
projectDataset
=
{
projectId
:
'
1
'
,
projectGid
:
'
gid://gitlab/Project/1
'
,
projectName
:
'
My Project
'
,
projectPathWithNamespace
:
'
my-group/my-project
'
,
};
...
...
@@ -66,7 +67,7 @@ describe('buildProjectFromDataset', () => {
it
(
'
returns a project object when the projectId is given
'
,
()
=>
{
expect
(
buildProjectFromDataset
(
projectDataset
)).
toEqual
({
id
:
1
,
id
:
'
gid://gitlab/Project/1
'
,
name
:
'
My Project
'
,
path_with_namespace
:
'
my-group/my-project
'
,
avatar_url
:
undefined
,
...
...
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