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
31a81957
Commit
31a81957
authored
Jul 16, 2020
by
Simon Knox
Committed by
Kushal Pandya
Jul 16, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add basic iteration report view
Filtering iterations by ID, and display just info No edit view for now
parent
d30d93cb
Changes
22
Hide whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
833 additions
and
9 deletions
+833
-9
app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql
...avascripts/graphql_shared/fragments/user.fragment.graphql
+7
-0
app/graphql/resolvers/issues_resolver.rb
app/graphql/resolvers/issues_resolver.rb
+2
-0
app/graphql/types/issue_connection_type.rb
app/graphql/types/issue_connection_type.rb
+13
-0
app/graphql/types/issue_type.rb
app/graphql/types/issue_type.rb
+2
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+25
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+78
-0
ee/app/assets/javascripts/iterations/components/iteration_report.vue
...ts/javascripts/iterations/components/iteration_report.vue
+3
-0
ee/app/assets/javascripts/iterations/components/iteration_report_tabs.vue
...vascripts/iterations/components/iteration_report_tabs.vue
+228
-0
ee/app/assets/javascripts/iterations/queries/iteration_issues.query.graphql
...scripts/iterations/queries/iteration_issues.query.graphql
+37
-0
ee/app/finders/ee/issues_finder.rb
ee/app/finders/ee/issues_finder.rb
+15
-1
ee/app/finders/ee/issues_finder/params.rb
ee/app/finders/ee/issues_finder/params.rb
+4
-0
ee/app/graphql/ee/resolvers/issues_resolver.rb
ee/app/graphql/ee/resolvers/issues_resolver.rb
+15
-0
ee/app/models/ee/issue.rb
ee/app/models/ee/issue.rb
+3
-0
ee/spec/features/groups/iterations/user_views_iteration_spec.rb
...c/features/groups/iterations/user_views_iteration_spec.rb
+16
-1
ee/spec/finders/issues_finder_spec.rb
ee/spec/finders/issues_finder_spec.rb
+50
-2
ee/spec/frontend/iterations/components/iteration_report_spec.js
...c/frontend/iterations/components/iteration_report_spec.js
+1
-0
ee/spec/frontend/iterations/components/iteration_report_tabs_spec.js
...ntend/iterations/components/iteration_report_tabs_spec.js
+167
-0
ee/spec/graphql/ee/resolvers/issues_resolver_spec.rb
ee/spec/graphql/ee/resolvers/issues_resolver_spec.rb
+16
-5
ee/spec/models/issue_spec.rb
ee/spec/models/issue_spec.rb
+33
-0
locale/gitlab.pot
locale/gitlab.pot
+9
-0
spec/graphql/types/issue_connection_type_spec.rb
spec/graphql/types/issue_connection_type_spec.rb
+11
-0
spec/graphql/types/issue_type_spec.rb
spec/graphql/types/issue_type_spec.rb
+98
-0
No files found.
app/assets/javascripts/graphql_shared/fragments/user.fragment.graphql
0 → 100644
View file @
31a81957
fragment
User
on
User
{
id
avatarUrl
name
username
webUrl
}
app/graphql/resolvers/issues_resolver.rb
View file @
31a81957
...
...
@@ -92,3 +92,5 @@ module Resolvers
end
end
end
Resolvers
::
IssuesResolver
.
prepend_if_ee
(
'::EE::Resolvers::IssuesResolver'
)
app/graphql/types/issue_connection_type.rb
0 → 100644
View file @
31a81957
# frozen_string_literal: true
module
Types
# rubocop: disable Graphql/AuthorizeTypes
class
IssueConnectionType
<
GraphQL
::
Types
::
Relay
::
BaseConnection
field
:count
,
Integer
,
null:
false
,
description:
'Total count of collection'
def
count
object
.
items
.
size
end
end
end
app/graphql/types/issue_type.rb
View file @
31a81957
...
...
@@ -4,6 +4,8 @@ module Types
class
IssueType
<
BaseObject
graphql_name
'Issue'
connection_type_class
(
Types
::
IssueConnectionType
)
implements
(
Types
::
Notes
::
NoteableType
)
authorize
:read_issue
...
...
doc/api/graphql/reference/gitlab_schema.graphql
View file @
31a81957
...
...
@@ -4370,6 +4370,11 @@ type EpicIssue implements Noteable {
The connection type for EpicIssue.
"""
type
EpicIssueConnection
{
"""
Total
count
of
collection
"""
count
:
Int
!
"""
A
list
of
edges
.
"""
...
...
@@ -5043,6 +5048,11 @@ type Group {
"""
iids
:
[
String
!]
"""
Iterations
applied
to
the
issue
"""
iterationId
:
[
ID
]
"""
Labels
applied
to
this
issue
"""
...
...
@@ -5967,6 +5977,11 @@ type Issue implements Noteable {
The connection type for Issue.
"""
type
IssueConnection
{
"""
Total
count
of
collection
"""
count
:
Int
!
"""
A
list
of
edges
.
"""
...
...
@@ -9153,6 +9168,11 @@ type Project {
"""
iids
:
[
String
!]
"""
Iterations
applied
to
the
issue
"""
iterationId
:
[
ID
]
"""
Labels
applied
to
this
issue
"""
...
...
@@ -9248,6 +9268,11 @@ type Project {
"""
iids
:
[
String
!]
"""
Iterations
applied
to
the
issue
"""
iterationId
:
[
ID
]
"""
Labels
applied
to
this
issue
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
31a81957
...
...
@@ -12216,6 +12216,24 @@
"name": "EpicIssueConnection",
"description": "The connection type for EpicIssue.",
"fields": [
{
"name": "count",
"description": "Total count of collection",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "edges",
"description": "A list of edges.",
...
...
@@ -14041,6 +14059,20 @@
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
...
...
@@ -16428,6 +16460,24 @@
"name": "IssueConnection",
"description": "The connection type for Issue.",
"fields": [
{
"name": "count",
"description": "Total count of collection",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "edges",
"description": "A list of edges.",
...
...
@@ -27282,6 +27332,20 @@
"ofType": null
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
...
...
@@ -27462,6 +27526,20 @@
},
"defaultValue": "created_desc"
},
{
"name": "iterationId",
"description": "Iterations applied to the issue",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
ee/app/assets/javascripts/iterations/components/iteration_report.vue
View file @
31a81957
...
...
@@ -11,6 +11,7 @@ import {
import
{
formatDate
}
from
'
~/lib/utils/datetime_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
IterationForm
from
'
./iteration_form.vue
'
;
import
IterationReportTabs
from
'
./iteration_report_tabs.vue
'
;
import
query
from
'
../queries/group_iteration.query.graphql
'
;
const
iterationStates
=
{
...
...
@@ -29,6 +30,7 @@ export default {
GlNewDropdown
,
GlNewDropdownItem
,
IterationForm
,
IterationReportTabs
,
},
apollo
:
{
group
:
{
...
...
@@ -154,6 +156,7 @@ export default {
</div>
<h3
ref=
"title"
class=
"page-title"
>
{{ iteration.title }}
</h3>
<div
ref=
"description"
v-html=
"iteration.description"
></div>
<iteration-report-tabs
:group-path=
"groupPath"
:iteration-id=
"iteration.id"
/>
</template>
</div>
</template>
ee/app/assets/javascripts/iterations/components/iteration_report_tabs.vue
0 → 100644
View file @
31a81957
<
script
>
import
{
GlAlert
,
GlAvatar
,
GlBadge
,
GlLink
,
GlLoadingIcon
,
GlPagination
,
GlTab
,
GlTabs
,
GlTable
,
GlTooltipDirective
,
}
from
'
@gitlab/ui
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
getIdFromGraphQLId
}
from
'
~/graphql_shared/utils
'
;
import
query
from
'
../queries/iteration_issues.query.graphql
'
;
const
states
=
{
opened
:
'
opened
'
,
closed
:
'
closed
'
,
};
const
pageSize
=
20
;
export
default
{
fields
:
[
{
key
:
'
title
'
,
label
:
__
(
'
Title
'
),
class
:
'
gl-bg-transparent! gl-border-b-1
'
,
},
{
key
:
'
status
'
,
label
:
__
(
'
Status
'
),
class
:
'
gl-bg-transparent! gl-text-truncate
'
,
thClass
:
'
gl-w-eighth
'
,
},
{
key
:
'
assignees
'
,
label
:
__
(
'
Assignees
'
),
class
:
'
gl-bg-transparent! gl-text-right
'
,
thClass
:
'
gl-w-eighth
'
,
},
],
components
:
{
GlAlert
,
GlAvatar
,
GlBadge
,
GlLink
,
GlLoadingIcon
,
GlPagination
,
GlTab
,
GlTabs
,
GlTable
,
},
directives
:
{
GlTooltip
:
GlTooltipDirective
,
},
apollo
:
{
issues
:
{
query
,
variables
()
{
return
this
.
queryVariables
;
},
update
(
data
)
{
const
{
nodes
:
issues
=
[],
count
,
pageInfo
=
{}
}
=
data
?.
group
?.
issues
||
{};
const
list
=
issues
.
map
(
issue
=>
({
...
issue
,
labels
:
issue
?.
labels
?.
nodes
||
[],
assignees
:
issue
?.
assignees
?.
nodes
||
[],
}));
return
{
pageInfo
,
list
,
count
,
};
},
error
()
{
this
.
error
=
__
(
'
Error loading issues
'
);
},
},
},
props
:
{
groupPath
:
{
type
:
String
,
required
:
true
,
},
iterationId
:
{
type
:
String
,
required
:
true
,
},
},
data
()
{
return
{
issues
:
{
list
:
[],
pageInfo
:
{
hasNextPage
:
true
,
hasPreviousPage
:
false
,
},
},
error
:
''
,
pagination
:
{
currentPage
:
1
,
},
};
},
computed
:
{
queryVariables
()
{
const
vars
=
{
groupPath
:
this
.
groupPath
,
id
:
getIdFromGraphQLId
(
this
.
iterationId
),
};
if
(
this
.
pagination
.
beforeCursor
)
{
vars
.
beforeCursor
=
this
.
pagination
.
beforeCursor
;
vars
.
lastPageSize
=
pageSize
;
}
else
{
vars
.
afterCursor
=
this
.
pagination
.
afterCursor
;
vars
.
firstPageSize
=
pageSize
;
}
return
vars
;
},
prevPage
()
{
return
Number
(
this
.
issues
.
pageInfo
.
hasPreviousPage
);
},
nextPage
()
{
return
Number
(
this
.
issues
.
pageInfo
.
hasNextPage
);
},
},
methods
:
{
tooltipText
(
assignee
)
{
return
sprintf
(
__
(
'
Assigned to %{assigneeName}
'
),
{
assigneeName
:
assignee
.
name
,
});
},
issueState
(
state
,
assigneeCount
)
{
if
(
state
===
states
.
opened
&&
assigneeCount
===
0
)
{
return
__
(
'
Open
'
);
}
if
(
state
===
states
.
opened
&&
assigneeCount
>
0
)
{
return
__
(
'
In progress
'
);
}
return
__
(
'
Closed
'
);
},
handlePageChange
(
page
)
{
const
{
startCursor
,
endCursor
}
=
this
.
issues
.
pageInfo
;
if
(
page
>
this
.
pagination
.
currentPage
)
{
this
.
pagination
=
{
afterCursor
:
endCursor
,
currentPage
:
page
,
};
}
else
{
this
.
pagination
=
{
beforeCursor
:
startCursor
,
currentPage
:
page
,
};
}
},
},
};
</
script
>
<
template
>
<gl-tabs>
<gl-alert
v-if=
"error"
variant=
"danger"
@
dismiss=
"error = ''"
>
{{
error
}}
</gl-alert>
<gl-tab
title=
"Issues"
>
<template
#title
>
<span>
{{
__
(
'
Issues
'
)
}}
</span
><gl-badge
class=
"ml-2"
variant=
"neutral"
>
{{
issues
.
count
}}
</gl-badge>
</
template
>
<gl-loading-icon
v-if=
"$apollo.queries.issues.loading"
class=
"gl-my-9"
size=
"md"
/>
<gl-table
v-else
:items=
"issues.list"
:fields=
"$options.fields"
:empty-text=
"__('No iterations found')"
:show-empty=
"true"
fixed
stacked=
"sm"
>
<
template
#cell(title)=
"{ item: { iid, title, webUrl } }"
>
<div
class=
"gl-text-truncate"
>
<gl-link
class=
"gl-text-gray-900 gl-font-weight-bold"
:href=
"webUrl"
>
{{
title
}}
</gl-link>
<!-- TODO: add references.relative (project name) -->
<!-- Depends on https://gitlab.com/gitlab-org/gitlab/-/issues/222763 -->
<div
class=
"gl-text-secondary"
>
#
{{
iid
}}
</div>
</div>
</
template
>
<
template
#cell(status)=
"{ item: { state, assignees = [] } }"
>
<span
class=
"gl-w-6 gl-flex-shrink-0"
>
{{
issueState
(
state
,
assignees
.
length
)
}}
</span>
</
template
>
<
template
#cell(assignees)=
"{ item: { assignees } }"
>
<span
class=
"assignee-icon gl-w-6"
>
<span
v-for=
"assignee in assignees"
:key=
"assignee.username"
v-gl-tooltip=
"tooltipText(assignee)"
>
<gl-avatar
:src=
"assignee.avatarUrl"
:size=
"16"
/>
</span>
</span>
</
template
>
</gl-table>
<div
class=
"mt-3"
>
<gl-pagination
:value=
"pagination.currentPage"
:prev-page=
"prevPage"
:next-page=
"nextPage"
align=
"center"
class=
"gl-pagination gl-mt-3"
@
input=
"handlePageChange"
/>
</div>
</gl-tab>
</gl-tabs>
</template>
ee/app/assets/javascripts/iterations/queries/iteration_issues.query.graphql
0 → 100644
View file @
31a81957
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query
GroupIteration
(
$groupPath
:
ID
!
$id
:
ID
!
$beforeCursor
:
String
=
""
$afterCursor
:
String
=
""
$firstPageSize
:
Int
$lastPageSize
:
Int
)
{
group
(
fullPath
:
$groupPath
)
{
issues
(
iterationId
:
[
$id
]
before
:
$beforeCursor
after
:
$afterCursor
first
:
$firstPageSize
last
:
$lastPageSize
)
{
count
pageInfo
{
...
PageInfo
}
nodes
{
iid
title
webUrl
state
assignees
{
nodes
{
...
User
}
}
}
}
}
}
ee/app/finders/ee/issues_finder.rb
View file @
31a81957
...
...
@@ -18,7 +18,8 @@ module EE
override
:filter_items
def
filter_items
(
items
)
issues
=
by_weight
(
super
)
by_epic
(
issues
)
issues
=
by_epic
(
issues
)
by_iteration
(
issues
)
end
private
...
...
@@ -61,5 +62,18 @@ module EE
items
.
in_epics
(
params
.
epics
)
end
end
def
by_iteration
(
items
)
return
items
unless
params
.
iterations
case
params
.
iterations
.
to_s
.
downcase
when
::
IssuableFinder
::
Params
::
FILTER_NONE
items
.
no_iteration
when
::
IssuableFinder
::
Params
::
FILTER_ANY
items
.
any_iteration
else
items
.
in_iterations
(
params
.
iterations
)
end
end
end
end
ee/app/finders/ee/issues_finder/params.rb
View file @
31a81957
...
...
@@ -50,6 +50,10 @@ module EE
params
[
:epic_id
]
end
end
def
iterations
params
[
:iteration_id
]
end
end
end
end
ee/app/graphql/ee/resolvers/issues_resolver.rb
0 → 100644
View file @
31a81957
# frozen_string_literal: true
module
EE
module
Resolvers
module
IssuesResolver
extend
ActiveSupport
::
Concern
prepended
do
argument
:iteration_id
,
::
GraphQL
::
ID_TYPE
.
to_list_type
,
required:
false
,
description:
'Iterations applied to the issue'
end
end
end
end
ee/app/models/ee/issue.rb
View file @
31a81957
...
...
@@ -25,6 +25,9 @@ module EE
issue_ids
=
EpicIssue
.
where
(
epic_id:
epics
).
select
(
:issue_id
)
id_in
(
issue_ids
)
end
scope
:no_iteration
,
->
{
where
(
sprint_id:
nil
)
}
scope
:any_iteration
,
->
{
where
.
not
(
sprint_id:
nil
)
}
scope
:in_iterations
,
->
(
iterations
)
{
where
(
sprint_id:
iterations
)
}
scope
:on_status_page
,
->
do
joins
(
project: :status_page_setting
)
.
where
(
status_page_settings:
{
enabled:
true
})
...
...
ee/spec/features/groups/iterations/user_views_iteration_spec.rb
View file @
31a81957
...
...
@@ -5,11 +5,19 @@ require 'spec_helper'
RSpec
.
describe
'User views iteration'
do
let_it_be
(
:now
)
{
Time
.
now
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:iteration
)
{
create
(
:iteration
,
:skip_future_date_validation
,
group:
group
,
start_date:
now
-
1
.
day
,
due_date:
now
)
}
let_it_be
(
:project
)
{
create
(
:project
,
group:
group
)
}
let_it_be
(
:user
)
{
create
(
:group_member
,
:maintainer
,
user:
create
(
:user
),
group:
group
).
user
}
let_it_be
(
:iteration
)
{
create
(
:iteration
,
:skip_future_date_validation
,
iid:
1
,
id:
2
,
group:
group
,
title:
'Correct Iteration'
,
start_date:
now
-
1
.
day
,
due_date:
now
)
}
let_it_be
(
:other_iteration
)
{
create
(
:iteration
,
:skip_future_date_validation
,
iid:
2
,
id:
1
,
group:
group
,
title:
'Wrong Iteration'
,
start_date:
now
-
4
.
days
,
due_date:
now
-
3
.
days
)
}
let_it_be
(
:issue
)
{
create
(
:issue
,
project:
project
,
iteration:
iteration
)
}
let_it_be
(
:assigned_issue
)
{
create
(
:issue
,
project:
project
,
iteration:
iteration
,
assignees:
[
user
])
}
let_it_be
(
:closed_issue
)
{
create
(
:closed_issue
,
project:
project
,
iteration:
iteration
)
}
let_it_be
(
:other_issue
)
{
create
(
:issue
,
project:
project
,
iteration:
other_iteration
)
}
context
'with license'
do
before
do
stub_licensed_features
(
iterations:
true
)
sign_in
(
user
)
end
context
'view an iteration'
,
:js
do
...
...
@@ -23,6 +31,13 @@ RSpec.describe 'User views iteration' do
expect
(
page
).
to
have_content
(
iteration
.
start_date
.
strftime
(
'%b %-d, %Y'
))
expect
(
page
).
to
have_content
(
iteration
.
due_date
.
strftime
(
'%b %-d, %Y'
))
end
it
'shows correct issues for issue'
do
expect
(
page
).
to
have_content
(
issue
.
title
)
expect
(
page
).
to
have_content
(
assigned_issue
.
title
)
expect
(
page
).
to
have_content
(
closed_issue
.
title
)
expect
(
page
).
not_to
have_content
(
other_issue
.
title
)
end
end
end
end
ee/spec/finders/issues_finder_spec.rb
View file @
31a81957
...
...
@@ -8,6 +8,8 @@ RSpec.describe IssuesFinder do
include_context
'IssuesFinder#execute context'
context
'scope: all'
do
let_it_be
(
:group
)
{
create
(
:group
)
}
let
(
:scope
)
{
'all'
}
describe
'filter by weight'
do
...
...
@@ -76,8 +78,6 @@ RSpec.describe IssuesFinder do
end
context
'filter by epic'
do
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:epic_1
)
{
create
(
:epic
,
group:
group
)
}
let_it_be
(
:epic_2
)
{
create
(
:epic
,
group:
group
)
}
let_it_be
(
:sub_epic
)
{
create
(
:epic
,
group:
group
,
parent:
epic_1
)
}
...
...
@@ -122,6 +122,54 @@ RSpec.describe IssuesFinder do
end
end
end
context
'filter by iteration'
do
let_it_be
(
:iteration_1
)
{
create
(
:iteration
,
group:
group
)
}
let_it_be
(
:iteration_2
)
{
create
(
:iteration
,
group:
group
)
}
let_it_be
(
:iteration_1_issue
)
{
create
(
:issue
,
project:
project1
,
iteration:
iteration_1
)
}
let_it_be
(
:iteration_2_issue
)
{
create
(
:issue
,
project:
project1
,
iteration:
iteration_2
)
}
context
'filter issues with no iteration'
do
let
(
:params
)
{
{
iteration_id:
::
IssuableFinder
::
Params
::
FILTER_NONE
}
}
it
'returns all issues without iterations'
do
expect
(
issues
).
to
contain_exactly
(
issue1
,
issue2
,
issue3
,
issue4
)
end
end
context
'filter issues with any iteration'
do
let
(
:params
)
{
{
iteration_id:
::
IssuableFinder
::
Params
::
FILTER_ANY
}
}
it
'returns filtered issues'
do
expect
(
issues
).
to
contain_exactly
(
iteration_1_issue
,
iteration_2_issue
)
end
end
context
'filter issues by iteration'
do
let
(
:params
)
{
{
iteration_id:
iteration_1
.
id
}
}
it
'returns all issues with the iteration'
do
expect
(
issues
).
to
contain_exactly
(
iteration_1_issue
)
end
end
context
'filter issues by multiple iterations'
do
let
(
:params
)
{
{
iteration_id:
[
iteration_1
.
id
,
iteration_2
.
id
]
}
}
it
'returns all issues with the iteration'
do
expect
(
issues
).
to
contain_exactly
(
iteration_1_issue
,
iteration_2_issue
)
end
end
context
'without iteration_id param'
do
let
(
:params
)
{
{
iteration_id:
nil
}
}
it
'returns unfiltered issues'
do
expect
(
issues
).
to
contain_exactly
(
issue1
,
issue2
,
issue3
,
issue4
,
iteration_1_issue
,
iteration_2_issue
)
end
end
end
end
end
...
...
ee/spec/frontend/iterations/components/iteration_report_spec.js
View file @
31a81957
...
...
@@ -58,6 +58,7 @@ describe('Iterations tabs', () => {
describe
(
'
item loaded
'
,
()
=>
{
const
iteration
=
{
title
:
'
June week 1
'
,
id
:
'
gid://gitlab/Iteration/2
'
,
description
:
'
The first week of June
'
,
startDate
:
'
2020-06-02
'
,
dueDate
:
'
2020-06-08
'
,
...
...
ee/spec/frontend/iterations/components/iteration_report_tabs_spec.js
0 → 100644
View file @
31a81957
import
IterationReportTabs
from
'
ee/iterations/components/iteration_report_tabs.vue
'
;
import
{
mount
}
from
'
@vue/test-utils
'
;
import
{
GlAlert
,
GlAvatar
,
GlLoadingIcon
,
GlPagination
,
GlTable
,
GlTab
}
from
'
@gitlab/ui
'
;
describe
(
'
Iterations report tabs
'
,
()
=>
{
let
wrapper
;
const
id
=
3
;
const
groupPath
=
'
gitlab-org
'
;
const
defaultProps
=
{
groupPath
,
iterationId
:
`gid://gitlab/Iteration/
${
id
}
`
,
};
const
mountComponent
=
({
props
=
defaultProps
,
loading
=
false
,
data
=
{}
}
=
{})
=>
{
wrapper
=
mount
(
IterationReportTabs
,
{
propsData
:
props
,
data
()
{
return
data
;
},
mocks
:
{
$apollo
:
{
queries
:
{
issues
:
{
loading
}
},
},
},
stubs
:
{
GlAvatar
,
GlTab
,
GlTable
,
},
});
};
afterEach
(()
=>
{
wrapper
.
destroy
();
wrapper
=
null
;
});
it
(
'
shows spinner while loading
'
,
()
=>
{
mountComponent
({
loading
:
true
,
});
expect
(
wrapper
.
contains
(
GlLoadingIcon
)).
toBe
(
true
);
expect
(
wrapper
.
contains
(
GlTable
)).
toBe
(
false
);
});
it
(
'
shows iterations list when not loading
'
,
()
=>
{
mountComponent
({
loading
:
false
,
});
expect
(
wrapper
.
contains
(
GlLoadingIcon
)).
toBe
(
false
);
expect
(
wrapper
.
contains
(
GlTable
)).
toBe
(
true
);
expect
(
wrapper
.
text
()).
toContain
(
'
No iterations found
'
);
});
it
(
'
shows error in a gl-alert
'
,
()
=>
{
const
error
=
'
Oh no!
'
;
mountComponent
({
data
:
{
error
,
},
});
expect
(
wrapper
.
find
(
GlAlert
).
text
()).
toContain
(
error
);
});
describe
(
'
with issues
'
,
()
=>
{
const
pageSize
=
20
;
const
totalIssues
=
pageSize
+
1
;
const
assignees
=
Array
(
totalIssues
)
.
fill
(
null
)
.
map
((
_
,
i
)
=>
({
id
:
i
,
name
:
`User
${
i
}
`
,
username
:
`user
${
i
}
`
,
state
:
'
active
'
,
avatarUrl
:
'
http://invalid/avatar.png
'
,
webUrl
:
`https://localhost:3000/user
${
i
}
`
,
}));
const
issues
=
Array
(
totalIssues
)
.
fill
(
null
)
.
map
((
_
,
i
)
=>
({
id
:
i
,
title
:
`Issue
${
i
}
`
,
assignees
:
assignees
.
slice
(
0
,
i
),
}));
const
findIssues
=
()
=>
wrapper
.
findAll
(
'
table tbody tr
'
);
const
findAssigneesForIssue
=
index
=>
findIssues
()
.
at
(
index
)
.
findAll
(
GlAvatar
);
beforeEach
(()
=>
{
mountComponent
();
wrapper
.
setData
({
issues
:
{
list
:
issues
,
pageInfo
:
{
hasNextPage
:
true
,
hasPreviousPage
:
false
,
startCursor
:
'
first-item
'
,
endCursor
:
'
last-item
'
,
},
count
:
issues
.
length
,
},
});
});
it
(
'
shows issue list in table
'
,
()
=>
{
expect
(
wrapper
.
contains
(
GlTable
)).
toBe
(
true
);
expect
(
findIssues
()).
toHaveLength
(
issues
.
length
);
});
it
(
'
shows assignees
'
,
()
=>
{
expect
(
findAssigneesForIssue
(
0
)).
toHaveLength
(
0
);
expect
(
findAssigneesForIssue
(
1
)).
toHaveLength
(
1
);
expect
(
findAssigneesForIssue
(
10
)).
toHaveLength
(
10
);
});
describe
(
'
pagination
'
,
()
=>
{
const
findPagination
=
()
=>
wrapper
.
find
(
GlPagination
);
const
setPage
=
page
=>
{
findPagination
().
vm
.
$emit
(
'
input
'
,
page
);
return
findPagination
().
vm
.
$nextTick
();
};
it
(
'
passes prev, next, and current page props
'
,
()
=>
{
expect
(
findPagination
().
exists
()).
toBe
(
true
);
expect
(
findPagination
().
props
()).
toEqual
(
expect
.
objectContaining
({
value
:
wrapper
.
vm
.
pagination
.
currentPage
,
prevPage
:
wrapper
.
vm
.
prevPage
,
nextPage
:
wrapper
.
vm
.
nextPage
,
}),
);
});
it
(
'
updates query variables when going to previous page
'
,
()
=>
{
return
setPage
(
1
).
then
(()
=>
{
expect
(
wrapper
.
vm
.
queryVariables
).
toEqual
({
beforeCursor
:
'
first-item
'
,
groupPath
,
id
,
lastPageSize
:
20
,
});
});
});
it
(
'
updates query variables when going to next page
'
,
()
=>
{
return
setPage
(
2
).
then
(()
=>
{
expect
(
wrapper
.
vm
.
queryVariables
).
toEqual
({
afterCursor
:
'
last-item
'
,
groupPath
,
id
,
firstPageSize
:
20
,
});
});
});
});
});
});
ee/spec/graphql/ee/resolvers/issues_resolver_spec.rb
View file @
31a81957
...
...
@@ -6,10 +6,15 @@ RSpec.describe Resolvers::IssuesResolver do
include
GraphqlHelpers
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:group
)
{
create
(
:group
)
}
let_it_be
(
:project
)
{
create
(
:project
,
namespace:
group
)
}
context
"with a project"
do
describe
'#resolve'
do
before
do
project
.
add_developer
(
current_user
)
end
describe
'sorting'
do
context
'when sorting by weight'
do
let_it_be
(
:weight_issue1
)
{
create
(
:issue
,
project:
project
,
weight:
5
)
}
...
...
@@ -17,10 +22,6 @@ RSpec.describe Resolvers::IssuesResolver do
let_it_be
(
:weight_issue3
)
{
create
(
:issue
,
project:
project
,
weight:
1
)
}
let_it_be
(
:weight_issue4
)
{
create
(
:issue
,
project:
project
,
weight:
nil
)
}
before
do
project
.
add_developer
(
current_user
)
end
it
'sorts issues ascending'
do
expect
(
resolve_issues
(
sort: :weight_asc
)).
to
eq
[
weight_issue3
,
weight_issue1
,
weight_issue4
,
weight_issue2
]
end
...
...
@@ -30,6 +31,16 @@ RSpec.describe Resolvers::IssuesResolver do
end
end
end
describe
'filtering by iteration'
do
let_it_be
(
:iteration1
)
{
create
(
:iteration
,
group:
group
)
}
let_it_be
(
:issue_with_iteration
)
{
create
(
:issue
,
project:
project
,
iteration:
iteration1
)
}
let_it_be
(
:issue_without_iteration
)
{
create
(
:issue
,
project:
project
)
}
it
'returns issues with iteration'
do
expect
(
resolve_issues
(
iteration_id:
iteration1
.
id
)).
to
eq
[
issue_with_iteration
]
end
end
end
end
...
...
ee/spec/models/issue_spec.rb
View file @
31a81957
...
...
@@ -130,6 +130,39 @@ RSpec.describe Issue do
end
end
end
context
'iterations'
do
let_it_be
(
:iteration1
)
{
create
(
:iteration
)
}
let_it_be
(
:iteration2
)
{
create
(
:iteration
)
}
let_it_be
(
:iteration1_issue
)
{
create
(
:issue
,
iteration:
iteration1
)
}
let_it_be
(
:iteration2_issue
)
{
create
(
:issue
,
iteration:
iteration2
)
}
let_it_be
(
:issue_no_iteration
)
{
create
(
:issue
)
}
before
do
stub_licensed_features
(
iterations:
true
)
end
describe
'.no_iteration'
do
it
'returns only issues without an iteration assigned'
do
expect
(
described_class
.
count
).
to
eq
3
expect
(
described_class
.
no_iteration
).
to
eq
[
issue_no_iteration
]
end
end
describe
'.any_iteration'
do
it
'returns only issues with an iteration assigned'
do
expect
(
described_class
.
count
).
to
eq
3
expect
(
described_class
.
any_iteration
).
to
eq
[
iteration1_issue
,
iteration2_issue
]
end
end
describe
'.in_iterations'
do
it
'returns only issues in selected iterations'
do
expect
(
described_class
.
count
).
to
eq
3
expect
(
described_class
.
in_iterations
([
iteration1
])).
to
eq
[
iteration1_issue
]
end
end
end
end
describe
'validations'
do
...
...
locale/gitlab.pot
View file @
31a81957
...
...
@@ -3226,6 +3226,9 @@ msgstr ""
msgid "Assigned Merge Requests"
msgstr ""
msgid "Assigned to %{assigneeName}"
msgstr ""
msgid "Assigned to %{assignee_name}"
msgstr ""
...
...
@@ -9345,6 +9348,9 @@ msgstr ""
msgid "Error loading file viewer."
msgstr ""
msgid "Error loading issues"
msgstr ""
msgid "Error loading last commit."
msgstr ""
...
...
@@ -15691,6 +15697,9 @@ msgstr ""
msgid "No iteration"
msgstr ""
msgid "No iterations found"
msgstr ""
msgid "No iterations to show"
msgstr ""
...
...
spec/graphql/types/issue_connection_type_spec.rb
0 → 100644
View file @
31a81957
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
GitlabSchema
.
types
[
'IssueConnection'
]
do
it
'has the expected fields'
do
expected_fields
=
%i[count page_info edges nodes]
expect
(
described_class
).
to
have_graphql_fields
(
*
expected_fields
)
end
end
spec/graphql/types/issue_type_spec.rb
View file @
31a81957
...
...
@@ -22,6 +22,104 @@ RSpec.describe GitlabSchema.types['Issue'] do
end
end
describe
'pagination and count'
do
let_it_be
(
:user
)
{
create
(
:user
)
}
let_it_be
(
:project
)
{
create
(
:project
,
:public
)
}
let_it_be
(
:now
)
{
Time
.
now
.
change
(
usec:
0
)
}
let_it_be
(
:issues
)
{
create_list
(
:issue
,
10
,
project:
project
,
created_at:
now
)
}
let
(
:count_path
)
{
%w(data project issues count)
}
let
(
:page_size
)
{
3
}
let
(
:query
)
do
<<~
GRAPHQL
query project($fullPath: ID!, $first: Int, $after: String) {
project(fullPath: $fullPath) {
issues(first: $first, after: $after) {
count
edges {
node {
iid
}
}
pageInfo {
endCursor
hasNextPage
}
}
}
}
GRAPHQL
end
subject
do
GitlabSchema
.
execute
(
query
,
context:
{
current_user:
user
},
variables:
{
fullPath:
project
.
full_path
,
first:
page_size
}
).
to_h
end
context
'when user does not have the permission'
do
it
'returns no data'
do
allow
(
Ability
).
to
receive
(
:allowed?
).
with
(
user
,
:read_project
,
project
).
and_return
(
false
)
expect
(
subject
.
dig
(
:data
,
:project
)).
to
eq
(
nil
)
end
end
context
'count'
do
let
(
:end_cursor
)
{
%w(data project issues pageInfo endCursor)
}
let
(
:issues_edges
)
{
%w(data project issues edges)
}
it
'returns total count'
do
expect
(
subject
.
dig
(
*
count_path
)).
to
eq
(
issues
.
count
)
end
it
'total count does not change between pages'
do
old_count
=
subject
.
dig
(
*
count_path
)
new_cursor
=
subject
.
dig
(
*
end_cursor
)
new_page
=
GitlabSchema
.
execute
(
query
,
context:
{
current_user:
user
},
variables:
{
fullPath:
project
.
full_path
,
first:
page_size
,
after:
new_cursor
}
).
to_h
new_count
=
new_page
.
dig
(
*
count_path
)
expect
(
old_count
).
to
eq
(
new_count
)
end
context
'pagination'
do
let
(
:page_size
)
{
9
}
it
'returns new ids during pagination'
do
old_edges
=
subject
.
dig
(
*
issues_edges
)
new_cursor
=
subject
.
dig
(
*
end_cursor
)
new_edges
=
GitlabSchema
.
execute
(
query
,
context:
{
current_user:
user
},
variables:
{
fullPath:
project
.
full_path
,
first:
page_size
,
after:
new_cursor
}
).
to_h
.
dig
(
*
issues_edges
)
expect
(
old_edges
.
count
).
to
eq
(
9
)
expect
(
new_edges
.
count
).
to
eq
(
1
)
end
end
end
end
describe
"issue notes"
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
,
:public
)
}
...
...
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