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
4b6bca3a
Commit
4b6bca3a
authored
Sep 21, 2020
by
David O'Regan
Committed by
Sean McGivern
Sep 21, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add incident token filter
Update incident filters to allow token based search with author and assignees
parent
59812e04
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
395 additions
and
56 deletions
+395
-56
app/assets/javascripts/incidents/components/incidents_list.vue
...ssets/javascripts/incidents/components/incidents_list.vue
+139
-19
app/assets/javascripts/incidents/constants.js
app/assets/javascripts/incidents/constants.js
+1
-2
app/assets/javascripts/incidents/graphql/queries/get_count_by_status.query.graphql
...cidents/graphql/queries/get_count_by_status.query.graphql
+13
-2
app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql
...pts/incidents/graphql/queries/get_incidents.query.graphql
+5
-1
app/assets/javascripts/incidents/list.js
app/assets/javascripts/incidents/list.js
+6
-0
app/graphql/resolvers/concerns/issue_resolver_arguments.rb
app/graphql/resolvers/concerns/issue_resolver_arguments.rb
+4
-1
app/helpers/projects/incidents_helper.rb
app/helpers/projects/incidents_helper.rb
+5
-2
app/views/projects/incidents/index.html.haml
app/views/projects/incidents/index.html.haml
+1
-1
changelogs/unreleased/229404-filter-capabilities.yml
changelogs/unreleased/229404-filter-capabilities.yml
+5
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+24
-4
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+80
-8
ee/app/helpers/ee/projects/incidents_helper.rb
ee/app/helpers/ee/projects/incidents_helper.rb
+1
-1
ee/spec/helpers/ee/projects/incidents_helper_spec.rb
ee/spec/helpers/ee/projects/incidents_helper_spec.rb
+12
-2
locale/gitlab.pot
locale/gitlab.pot
+0
-3
spec/frontend/incidents/components/incidents_list_spec.js
spec/frontend/incidents/components/incidents_list_spec.js
+62
-8
spec/frontend/incidents/mocks/incidents_filter.json
spec/frontend/incidents/mocks/incidents_filter.json
+14
-0
spec/graphql/resolvers/issues_resolver_spec.rb
spec/graphql/resolvers/issues_resolver_spec.rb
+11
-0
spec/helpers/projects/incidents_helper_spec.rb
spec/helpers/projects/incidents_helper_spec.rb
+12
-2
No files found.
app/assets/javascripts/incidents/components/incidents_list.vue
View file @
4b6bca3a
...
@@ -8,7 +8,6 @@ import {
...
@@ -8,7 +8,6 @@ import {
GlAvatar
,
GlAvatar
,
GlTooltipDirective
,
GlTooltipDirective
,
GlButton
,
GlButton
,
GlSearchBoxByType
,
GlIcon
,
GlIcon
,
GlPagination
,
GlPagination
,
GlTabs
,
GlTabs
,
...
@@ -16,16 +15,25 @@ import {
...
@@ -16,16 +15,25 @@ import {
GlBadge
,
GlBadge
,
GlEmptyState
,
GlEmptyState
,
}
from
'
@gitlab/ui
'
;
}
from
'
@gitlab/ui
'
;
import
{
debounce
}
from
'
lodash
'
;
import
Api
from
'
~/api
'
;
import
TimeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
TimeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
{
convertToSnakeCase
}
from
'
~/lib/utils/text_utility
'
;
import
{
convertToSnakeCase
}
from
'
~/lib/utils/text_utility
'
;
import
{
s__
}
from
'
~/locale
'
;
import
{
s__
,
__
}
from
'
~/locale
'
;
import
{
mergeUrlParams
,
joinPaths
,
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
urlParamsToObject
}
from
'
~/lib/utils/common_utils
'
;
import
{
visitUrl
,
mergeUrlParams
,
joinPaths
,
updateHistory
,
setUrlParams
,
}
from
'
~/lib/utils/url_utility
'
;
import
getIncidents
from
'
../graphql/queries/get_incidents.query.graphql
'
;
import
getIncidents
from
'
../graphql/queries/get_incidents.query.graphql
'
;
import
getIncidentsCountByStatus
from
'
../graphql/queries/get_count_by_status.query.graphql
'
;
import
getIncidentsCountByStatus
from
'
../graphql/queries/get_count_by_status.query.graphql
'
;
import
SeverityToken
from
'
~/sidebar/components/severity/severity.vue
'
;
import
SeverityToken
from
'
~/sidebar/components/severity/severity.vue
'
;
import
{
INCIDENT_SEVERITY
}
from
'
~/sidebar/components/severity/constants
'
;
import
{
INCIDENT_SEVERITY
}
from
'
~/sidebar/components/severity/constants
'
;
import
{
I18N
,
DEFAULT_PAGE_SIZE
,
INCIDENT_S
EARCH_DELAY
,
INCIDENT_S
TATUS_TABS
}
from
'
../constants
'
;
import
{
I18N
,
DEFAULT_PAGE_SIZE
,
INCIDENT_STATUS_TABS
}
from
'
../constants
'
;
const
TH_TEST_ID
=
{
'
data-testid
'
:
'
incident-management-created-at-sort
'
};
const
TH_TEST_ID
=
{
'
data-testid
'
:
'
incident-management-created-at-sort
'
};
const
tdClass
=
const
tdClass
=
...
@@ -82,7 +90,6 @@ export default {
...
@@ -82,7 +90,6 @@ export default {
GlAvatar
,
GlAvatar
,
GlButton
,
GlButton
,
TimeAgoTooltip
,
TimeAgoTooltip
,
GlSearchBoxByType
,
GlIcon
,
GlIcon
,
GlPagination
,
GlPagination
,
GlTabs
,
GlTabs
,
...
@@ -91,6 +98,7 @@ export default {
...
@@ -91,6 +98,7 @@ export default {
GlBadge
,
GlBadge
,
GlEmptyState
,
GlEmptyState
,
SeverityToken
,
SeverityToken
,
FilteredSearchBar
,
},
},
directives
:
{
directives
:
{
GlTooltip
:
GlTooltipDirective
,
GlTooltip
:
GlTooltipDirective
,
...
@@ -103,6 +111,9 @@ export default {
...
@@ -103,6 +111,9 @@ export default {
'
issuePath
'
,
'
issuePath
'
,
'
publishedAvailable
'
,
'
publishedAvailable
'
,
'
emptyListSvgPath
'
,
'
emptyListSvgPath
'
,
'
textQuery
'
,
'
authorUsernamesQuery
'
,
'
assigneeUsernamesQuery
'
,
],
],
apollo
:
{
apollo
:
{
incidents
:
{
incidents
:
{
...
@@ -118,6 +129,8 @@ export default {
...
@@ -118,6 +129,8 @@ export default {
lastPageSize
:
this
.
pagination
.
lastPageSize
,
lastPageSize
:
this
.
pagination
.
lastPageSize
,
prevPageCursor
:
this
.
pagination
.
prevPageCursor
,
prevPageCursor
:
this
.
pagination
.
prevPageCursor
,
nextPageCursor
:
this
.
pagination
.
nextPageCursor
,
nextPageCursor
:
this
.
pagination
.
nextPageCursor
,
authorUsername
:
this
.
authorUsername
,
assigneeUsernames
:
this
.
assigneeUsernames
,
};
};
},
},
update
({
project
:
{
issues
:
{
nodes
=
[],
pageInfo
=
{}
}
=
{}
}
=
{}
})
{
update
({
project
:
{
issues
:
{
nodes
=
[],
pageInfo
=
{}
}
=
{}
}
=
{}
})
{
...
@@ -135,6 +148,8 @@ export default {
...
@@ -135,6 +148,8 @@ export default {
variables
()
{
variables
()
{
return
{
return
{
searchTerm
:
this
.
searchTerm
,
searchTerm
:
this
.
searchTerm
,
authorUsername
:
this
.
authorUsername
,
assigneeUsernames
:
this
.
assigneeUsernames
,
projectPath
:
this
.
projectPath
,
projectPath
:
this
.
projectPath
,
issueTypes
:
[
'
INCIDENT
'
],
issueTypes
:
[
'
INCIDENT
'
],
};
};
...
@@ -149,7 +164,7 @@ export default {
...
@@ -149,7 +164,7 @@ export default {
errored
:
false
,
errored
:
false
,
isErrorAlertDismissed
:
false
,
isErrorAlertDismissed
:
false
,
redirecting
:
false
,
redirecting
:
false
,
searchTerm
:
''
,
searchTerm
:
this
.
textQuery
,
pagination
:
initialPaginationState
,
pagination
:
initialPaginationState
,
incidents
:
{},
incidents
:
{},
sort
:
'
created_desc
'
,
sort
:
'
created_desc
'
,
...
@@ -157,6 +172,9 @@ export default {
...
@@ -157,6 +172,9 @@ export default {
sortDesc
:
true
,
sortDesc
:
true
,
statusFilter
:
''
,
statusFilter
:
''
,
filteredByStatus
:
''
,
filteredByStatus
:
''
,
authorUsername
:
this
.
authorUsernamesQuery
,
assigneeUsernames
:
this
.
assigneeUsernamesQuery
,
filterParams
:
{},
};
};
},
},
computed
:
{
computed
:
{
...
@@ -242,14 +260,57 @@ export default {
...
@@ -242,14 +260,57 @@ export default {
btnText
:
createIncidentBtnLabel
,
btnText
:
createIncidentBtnLabel
,
};
};
},
},
filteredSearchTokens
()
{
return
[
{
type
:
'
author_username
'
,
icon
:
'
user
'
,
title
:
__
(
'
Author
'
),
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
:
[{
value
:
'
=
'
,
description
:
__
(
'
is
'
),
default
:
'
true
'
}],
fetchPath
:
this
.
projectPath
,
fetchAuthors
:
Api
.
projectUsers
.
bind
(
Api
),
},
},
methods
:
{
{
onInputChange
:
debounce
(
function
debounceSearch
(
input
)
{
type
:
'
assignee_username
'
,
const
trimmedInput
=
input
.
trim
();
icon
:
'
user
'
,
if
(
trimmedInput
!==
this
.
searchTerm
)
{
title
:
__
(
'
Assignees
'
),
this
.
searchTerm
=
trimmedInput
;
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
:
[{
value
:
'
=
'
,
description
:
__
(
'
is
'
),
default
:
'
true
'
}],
fetchPath
:
this
.
projectPath
,
fetchAuthors
:
Api
.
projectUsers
.
bind
(
Api
),
},
];
},
filteredSearchValue
()
{
const
value
=
[];
if
(
this
.
authorUsername
)
{
value
.
push
({
type
:
'
author_username
'
,
value
:
{
data
:
this
.
authorUsername
},
});
}
if
(
this
.
assigneeUsernames
)
{
value
.
push
({
type
:
'
assignee_username
'
,
value
:
{
data
:
this
.
assigneeUsernames
},
});
}
}
},
INCIDENT_SEARCH_DELAY
),
if
(
this
.
searchTerm
)
{
value
.
push
(
this
.
searchTerm
);
}
return
value
;
},
},
methods
:
{
filterIncidentsByStatus
(
tabIndex
)
{
filterIncidentsByStatus
(
tabIndex
)
{
const
{
filters
,
status
}
=
this
.
$options
.
statusTabs
[
tabIndex
];
const
{
filters
,
status
}
=
this
.
$options
.
statusTabs
[
tabIndex
];
this
.
statusFilter
=
filters
;
this
.
statusFilter
=
filters
;
...
@@ -292,6 +353,61 @@ export default {
...
@@ -292,6 +353,61 @@ export default {
getSeverity
(
severity
)
{
getSeverity
(
severity
)
{
return
INCIDENT_SEVERITY
[
severity
];
return
INCIDENT_SEVERITY
[
severity
];
},
},
handleFilterIncidents
(
filters
)
{
const
filterParams
=
{
authorUsername
:
''
,
assigneeUsername
:
[],
search
:
''
};
filters
.
forEach
(
filter
=>
{
if
(
typeof
filter
===
'
object
'
)
{
switch
(
filter
.
type
)
{
case
'
author_username
'
:
filterParams
.
authorUsername
=
filter
.
value
.
data
;
break
;
case
'
assignee_username
'
:
filterParams
.
assigneeUsername
.
push
(
filter
.
value
.
data
);
break
;
case
'
filtered-search-term
'
:
if
(
filter
.
value
.
data
!==
''
)
filterParams
.
search
=
filter
.
value
.
data
;
break
;
default
:
break
;
}
}
});
this
.
filterParams
=
filterParams
;
this
.
updateUrl
();
this
.
searchTerm
=
filterParams
?.
search
;
this
.
authorUsername
=
filterParams
?.
authorUsername
;
this
.
assigneeUsernames
=
filterParams
?.
assigneeUsername
;
},
updateUrl
()
{
const
queryParams
=
urlParamsToObject
(
window
.
location
.
search
);
const
{
authorUsername
,
assigneeUsername
,
search
}
=
this
.
filterParams
||
{};
if
(
authorUsername
)
{
queryParams
.
author_username
=
authorUsername
;
}
else
{
delete
queryParams
.
author_username
;
}
if
(
assigneeUsername
)
{
queryParams
.
assignee_username
=
assigneeUsername
;
}
else
{
delete
queryParams
.
assignee_username
;
}
if
(
search
)
{
queryParams
.
search
=
search
;
}
else
{
delete
queryParams
.
search
;
}
updateHistory
({
url
:
setUrlParams
(
queryParams
,
window
.
location
.
href
,
true
),
title
:
document
.
title
,
replace
:
true
,
});
},
},
},
};
};
</
script
>
</
script
>
...
@@ -331,12 +447,16 @@ export default {
...
@@ -331,12 +447,16 @@ export default {
</gl-button>
</gl-button>
</div>
</div>
<div
class=
"gl-bg-gray-10 gl-p-5 gl-border-b-solid gl-border-b-1 gl-border-gray-100"
>
<div
class=
"filtered-search-wrapper"
>
<gl-search-box-by-type
<filtered-search-bar
:value=
"searchTerm"
:namespace=
"projectPath"
class=
"gl-bg-white"
:search-input-placeholder=
"$options.i18n.searchPlaceholder"
:placeholder=
"$options.i18n.searchPlaceholder"
:tokens=
"filteredSearchTokens"
@
input=
"onInputChange"
:initial-filter-value=
"filteredSearchValue"
initial-sortby=
"created_desc"
recent-searches-storage-key=
"incidents"
class=
"row-content-block"
@
onFilter=
"handleFilterIncidents"
/>
/>
</div>
</div>
...
...
app/assets/javascripts/incidents/constants.js
View file @
4b6bca3a
...
@@ -6,7 +6,7 @@ export const I18N = {
...
@@ -6,7 +6,7 @@ export const I18N = {
unassigned
:
s__
(
'
IncidentManagement|Unassigned
'
),
unassigned
:
s__
(
'
IncidentManagement|Unassigned
'
),
createIncidentBtnLabel
:
s__
(
'
IncidentManagement|Create incident
'
),
createIncidentBtnLabel
:
s__
(
'
IncidentManagement|Create incident
'
),
unPublished
:
s__
(
'
IncidentManagement|Unpublished
'
),
unPublished
:
s__
(
'
IncidentManagement|Unpublished
'
),
searchPlaceholder
:
__
(
'
Search results…
'
),
searchPlaceholder
:
__
(
'
Search
or filter
results…
'
),
emptyState
:
{
emptyState
:
{
title
:
s__
(
'
IncidentManagement|Display your incidents in a dedicated view
'
),
title
:
s__
(
'
IncidentManagement|Display your incidents in a dedicated view
'
),
emptyClosedTabTitle
:
s__
(
'
IncidentManagement|There are no closed incidents
'
),
emptyClosedTabTitle
:
s__
(
'
IncidentManagement|There are no closed incidents
'
),
...
@@ -34,5 +34,4 @@ export const INCIDENT_STATUS_TABS = [
...
@@ -34,5 +34,4 @@ export const INCIDENT_STATUS_TABS = [
},
},
];
];
export
const
INCIDENT_SEARCH_DELAY
=
300
;
export
const
DEFAULT_PAGE_SIZE
=
20
;
export
const
DEFAULT_PAGE_SIZE
=
20
;
app/assets/javascripts/incidents/graphql/queries/get_count_by_status.query.graphql
View file @
4b6bca3a
query
getIncidentsCountByStatus
(
$searchTerm
:
String
,
$projectPath
:
ID
!,
$issueTypes
:
[
IssueType
!])
{
query
getIncidentsCountByStatus
(
$searchTerm
:
String
$projectPath
:
ID
!
$issueTypes
:
[
IssueType
!]
$authorUsername
:
String
=
""
$assigneeUsernames
:
[
String
!]
=
[]
)
{
project
(
fullPath
:
$projectPath
)
{
project
(
fullPath
:
$projectPath
)
{
issueStatusCounts
(
search
:
$searchTerm
,
types
:
$issueTypes
)
{
issueStatusCounts
(
search
:
$searchTerm
types
:
$issueTypes
authorUsername
:
$authorUsername
assigneeUsername
:
$assigneeUsernames
)
{
all
all
opened
opened
closed
closed
...
...
app/assets/javascripts/incidents/graphql/queries/get_incidents.query.graphql
View file @
4b6bca3a
...
@@ -9,7 +9,9 @@ query getIncidents(
...
@@ -9,7 +9,9 @@ query getIncidents(
$lastPageSize
:
Int
$lastPageSize
:
Int
$prevPageCursor
:
String
=
""
$prevPageCursor
:
String
=
""
$nextPageCursor
:
String
=
""
$nextPageCursor
:
String
=
""
$searchTerm
:
String
$searchTerm
:
String
=
""
$authorUsername
:
String
=
""
$assigneeUsernames
:
[
String
!]
=
[]
)
{
)
{
project
(
fullPath
:
$projectPath
)
{
project
(
fullPath
:
$projectPath
)
{
issues
(
issues
(
...
@@ -17,6 +19,8 @@ query getIncidents(
...
@@ -17,6 +19,8 @@ query getIncidents(
types
:
$issueTypes
types
:
$issueTypes
sort
:
$sort
sort
:
$sort
state
:
$status
state
:
$status
authorUsername
:
$authorUsername
assigneeUsername
:
$assigneeUsernames
first
:
$firstPageSize
first
:
$firstPageSize
last
:
$lastPageSize
last
:
$lastPageSize
after
:
$nextPageCursor
after
:
$nextPageCursor
...
...
app/assets/javascripts/incidents/list.js
View file @
4b6bca3a
...
@@ -16,6 +16,9 @@ export default () => {
...
@@ -16,6 +16,9 @@ export default () => {
issuePath
,
issuePath
,
publishedAvailable
,
publishedAvailable
,
emptyListSvgPath
,
emptyListSvgPath
,
textQuery
,
authorUsernamesQuery
,
assigneeUsernamesQuery
,
}
=
domEl
.
dataset
;
}
=
domEl
.
dataset
;
const
apolloProvider
=
new
VueApollo
({
const
apolloProvider
=
new
VueApollo
({
...
@@ -32,6 +35,9 @@ export default () => {
...
@@ -32,6 +35,9 @@ export default () => {
issuePath
,
issuePath
,
publishedAvailable
,
publishedAvailable
,
emptyListSvgPath
,
emptyListSvgPath
,
textQuery
,
authorUsernamesQuery
,
assigneeUsernamesQuery
,
},
},
apolloProvider
,
apolloProvider
,
components
:
{
components
:
{
...
...
app/graphql/resolvers/concerns/issue_resolver_arguments.rb
View file @
4b6bca3a
...
@@ -18,7 +18,10 @@ module IssueResolverArguments
...
@@ -18,7 +18,10 @@ module IssueResolverArguments
argument
:milestone_title
,
GraphQL
::
STRING_TYPE
.
to_list_type
,
argument
:milestone_title
,
GraphQL
::
STRING_TYPE
.
to_list_type
,
required:
false
,
required:
false
,
description:
'Milestone applied to this issue'
description:
'Milestone applied to this issue'
argument
:assignee_username
,
GraphQL
::
STRING_TYPE
,
argument
:author_username
,
GraphQL
::
STRING_TYPE
,
required:
false
,
description:
'Username of the author of the issue'
argument
:assignee_username
,
[
GraphQL
::
STRING_TYPE
],
required:
false
,
required:
false
,
description:
'Username of a user assigned to the issue'
description:
'Username of a user assigned to the issue'
argument
:assignee_id
,
GraphQL
::
STRING_TYPE
,
argument
:assignee_id
,
GraphQL
::
STRING_TYPE
,
...
...
app/helpers/projects/incidents_helper.rb
View file @
4b6bca3a
# frozen_string_literal: true
# frozen_string_literal: true
module
Projects::IncidentsHelper
module
Projects::IncidentsHelper
def
incidents_data
(
project
)
def
incidents_data
(
project
,
params
)
{
{
'project-path'
=>
project
.
full_path
,
'project-path'
=>
project
.
full_path
,
'new-issue-path'
=>
new_project_issue_path
(
project
),
'new-issue-path'
=>
new_project_issue_path
(
project
),
'incident-template-name'
=>
'incident'
,
'incident-template-name'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'issue-path'
=>
project_issues_path
(
project
),
'issue-path'
=>
project_issues_path
(
project
),
'empty-list-svg-path'
=>
image_path
(
'illustrations/incident-empty-state.svg'
)
'empty-list-svg-path'
=>
image_path
(
'illustrations/incident-empty-state.svg'
),
'text-query'
:
params
[
:search
],
'author-usernames-query'
:
params
[
:author_username
],
'assignee-usernames-query'
:
params
[
:assignee_username
]
}
}
end
end
end
end
...
...
app/views/projects/incidents/index.html.haml
View file @
4b6bca3a
-
page_title
_
(
'Incidents'
)
-
page_title
_
(
'Incidents'
)
#js-incidents
{
data:
incidents_data
(
@project
)
}
#js-incidents
{
data:
incidents_data
(
@project
,
params
)
}
changelogs/unreleased/229404-filter-capabilities.yml
0 → 100644
View file @
4b6bca3a
---
title
:
Resolve Add filter capabilities to Incident list
merge_request
:
42377
author
:
type
:
changed
doc/api/graphql/reference/gitlab_schema.graphql
View file @
4b6bca3a
...
@@ -6778,7 +6778,12 @@ type Group {
...
@@ -6778,7 +6778,12 @@ type Group {
"""
"""
Username
of
a
user
assigned
to
the
issue
Username
of
a
user
assigned
to
the
issue
"""
"""
assigneeUsername
:
String
assigneeUsername
:
[
String
!]
"""
Username
of
the
author
of
the
issue
"""
authorUsername
:
String
"""
"""
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
...
@@ -12248,7 +12253,12 @@ type Project {
...
@@ -12248,7 +12253,12 @@ type Project {
"""
"""
Username
of
a
user
assigned
to
the
issue
Username
of
a
user
assigned
to
the
issue
"""
"""
assigneeUsername
:
String
assigneeUsername
:
[
String
!]
"""
Username
of
the
author
of
the
issue
"""
authorUsername
:
String
"""
"""
Issues
closed
after
this
date
Issues
closed
after
this
date
...
@@ -12338,7 +12348,12 @@ type Project {
...
@@ -12338,7 +12348,12 @@ type Project {
"""
"""
Username
of
a
user
assigned
to
the
issue
Username
of
a
user
assigned
to
the
issue
"""
"""
assigneeUsername
:
String
assigneeUsername
:
[
String
!]
"""
Username
of
the
author
of
the
issue
"""
authorUsername
:
String
"""
"""
Issues
closed
after
this
date
Issues
closed
after
this
date
...
@@ -12418,7 +12433,12 @@ type Project {
...
@@ -12418,7 +12433,12 @@ type Project {
"""
"""
Username
of
a
user
assigned
to
the
issue
Username
of
a
user
assigned
to
the
issue
"""
"""
assigneeUsername
:
String
assigneeUsername
:
[
String
!]
"""
Username
of
the
author
of
the
issue
"""
authorUsername
:
String
"""
"""
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
4b6bca3a
...
@@ -18848,13 +18848,31 @@
...
@@ -18848,13 +18848,31 @@
},
},
"defaultValue": null
"defaultValue": null
},
},
{
"name": "authorUsername",
"description": "Username of the author of the issue",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
{
"name": "assigneeUsername",
"name": "assigneeUsername",
"description": "Username of a user assigned to the issue",
"description": "Username of a user assigned to the issue",
"type": {
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"kind": "SCALAR",
"name": "String",
"name": "String",
"ofType": null
"ofType": null
}
}
},
},
"defaultValue": null
"defaultValue": null
},
},
...
@@ -36419,13 +36437,31 @@
...
@@ -36419,13 +36437,31 @@
},
},
"defaultValue": null
"defaultValue": null
},
},
{
"name": "authorUsername",
"description": "Username of the author of the issue",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
{
"name": "assigneeUsername",
"name": "assigneeUsername",
"description": "Username of a user assigned to the issue",
"description": "Username of a user assigned to the issue",
"type": {
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"kind": "SCALAR",
"name": "String",
"name": "String",
"ofType": null
"ofType": null
}
}
},
},
"defaultValue": null
"defaultValue": null
},
},
...
@@ -36630,13 +36666,31 @@
...
@@ -36630,13 +36666,31 @@
},
},
"defaultValue": null
"defaultValue": null
},
},
{
"name": "authorUsername",
"description": "Username of the author of the issue",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
{
"name": "assigneeUsername",
"name": "assigneeUsername",
"description": "Username of a user assigned to the issue",
"description": "Username of a user assigned to the issue",
"type": {
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"kind": "SCALAR",
"name": "String",
"name": "String",
"ofType": null
"ofType": null
}
}
},
},
"defaultValue": null
"defaultValue": null
},
},
...
@@ -36807,13 +36861,31 @@
...
@@ -36807,13 +36861,31 @@
},
},
"defaultValue": null
"defaultValue": null
},
},
{
"name": "authorUsername",
"description": "Username of the author of the issue",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
{
"name": "assigneeUsername",
"name": "assigneeUsername",
"description": "Username of a user assigned to the issue",
"description": "Username of a user assigned to the issue",
"type": {
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"kind": "SCALAR",
"name": "String",
"name": "String",
"ofType": null
"ofType": null
}
}
},
},
"defaultValue": null
"defaultValue": null
},
},
ee/app/helpers/ee/projects/incidents_helper.rb
View file @
4b6bca3a
...
@@ -6,7 +6,7 @@ module EE
...
@@ -6,7 +6,7 @@ module EE
extend
::
Gitlab
::
Utils
::
Override
extend
::
Gitlab
::
Utils
::
Override
override
:incidents_data
override
:incidents_data
def
incidents_data
(
project
)
def
incidents_data
(
project
,
params
)
super
.
merge
(
super
.
merge
(
incidents_data_published_available
(
project
)
incidents_data_published_available
(
project
)
)
)
...
...
ee/spec/helpers/ee/projects/incidents_helper_spec.rb
View file @
4b6bca3a
...
@@ -9,6 +9,13 @@ RSpec.describe Projects::IncidentsHelper do
...
@@ -9,6 +9,13 @@ RSpec.describe Projects::IncidentsHelper do
let
(
:project_path
)
{
project
.
full_path
}
let
(
:project_path
)
{
project
.
full_path
}
let
(
:new_issue_path
)
{
new_project_issue_path
(
project
)
}
let
(
:new_issue_path
)
{
new_project_issue_path
(
project
)
}
let
(
:issue_path
)
{
project_issues_path
(
project
)
}
let
(
:issue_path
)
{
project_issues_path
(
project
)
}
let
(
:params
)
do
{
search:
'search text'
,
author_username:
'root'
,
assignee_username:
'max.power'
}
end
describe
'#incidents_data'
do
describe
'#incidents_data'
do
let
(
:expected_incidents_data
)
do
let
(
:expected_incidents_data
)
do
...
@@ -18,11 +25,14 @@ RSpec.describe Projects::IncidentsHelper do
...
@@ -18,11 +25,14 @@ RSpec.describe Projects::IncidentsHelper do
'incident-template-name'
=>
'incident'
,
'incident-template-name'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'issue-path'
=>
issue_path
,
'issue-path'
=>
issue_path
,
'empty-list-svg-path'
=>
match_asset_path
(
'/assets/illustrations/incident-empty-state.svg'
)
'empty-list-svg-path'
=>
match_asset_path
(
'/assets/illustrations/incident-empty-state.svg'
),
'text-query'
:
'search text'
,
'author-usernames-query'
:
'root'
,
'assignee-usernames-query'
:
'max.power'
}
}
end
end
subject
{
helper
.
incidents_data
(
project
)
}
subject
{
helper
.
incidents_data
(
project
,
params
)
}
before
do
before
do
allow
(
project
).
to
receive
(
:feature_available?
).
with
(
:status_page
).
and_return
(
status_page_feature_available
)
allow
(
project
).
to
receive
(
:feature_available?
).
with
(
:status_page
).
and_return
(
status_page_feature_available
)
...
...
locale/gitlab.pot
View file @
4b6bca3a
...
@@ -22242,9 +22242,6 @@ msgstr ""
...
@@ -22242,9 +22242,6 @@ msgstr ""
msgid "Search requirements"
msgid "Search requirements"
msgstr ""
msgstr ""
msgid "Search results…"
msgstr ""
msgid "Search test cases"
msgid "Search test cases"
msgstr ""
msgstr ""
...
...
spec/frontend/incidents/components/incidents_list_spec.js
View file @
4b6bca3a
...
@@ -5,7 +5,6 @@ import {
...
@@ -5,7 +5,6 @@ import {
GlTable
,
GlTable
,
GlAvatar
,
GlAvatar
,
GlPagination
,
GlPagination
,
GlSearchBoxByType
,
GlTab
,
GlTab
,
GlTabs
,
GlTabs
,
GlBadge
,
GlBadge
,
...
@@ -15,13 +14,18 @@ import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
...
@@ -15,13 +14,18 @@ import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
import
IncidentsList
from
'
~/incidents/components/incidents_list.vue
'
;
import
IncidentsList
from
'
~/incidents/components/incidents_list.vue
'
;
import
SeverityToken
from
'
~/sidebar/components/severity/severity.vue
'
;
import
SeverityToken
from
'
~/sidebar/components/severity/severity.vue
'
;
import
TimeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
TimeAgoTooltip
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
FilteredSearchBar
from
'
~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
'
;
import
AuthorToken
from
'
~/vue_shared/components/filtered_search_bar/tokens/author_token.vue
'
;
import
{
I18N
,
INCIDENT_STATUS_TABS
}
from
'
~/incidents/constants
'
;
import
{
I18N
,
INCIDENT_STATUS_TABS
}
from
'
~/incidents/constants
'
;
import
mockIncidents
from
'
../mocks/incidents.json
'
;
import
mockIncidents
from
'
../mocks/incidents.json
'
;
import
mockFilters
from
'
../mocks/incidents_filter.json
'
;
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
({
jest
.
mock
(
'
~/lib/utils/url_utility
'
,
()
=>
({
visitUrl
:
jest
.
fn
().
mockName
(
'
visitUrlMock
'
),
visitUrl
:
jest
.
fn
().
mockName
(
'
visitUrlMock
'
),
joinPaths
:
jest
.
fn
().
mockName
(
'
joinPaths
'
),
joinPaths
:
jest
.
fn
().
mockName
(
'
joinPaths
'
),
mergeUrlParams
:
jest
.
fn
().
mockName
(
'
mergeUrlParams
'
),
mergeUrlParams
:
jest
.
fn
().
mockName
(
'
mergeUrlParams
'
),
setUrlParams
:
jest
.
fn
().
mockName
(
'
setUrlParams
'
),
updateHistory
:
jest
.
fn
().
mockName
(
'
updateHistory
'
),
}));
}));
describe
(
'
Incidents List
'
,
()
=>
{
describe
(
'
Incidents List
'
,
()
=>
{
...
@@ -43,7 +47,7 @@ describe('Incidents List', () => {
...
@@ -43,7 +47,7 @@ describe('Incidents List', () => {
const
findTimeAgo
=
()
=>
wrapper
.
findAll
(
TimeAgoTooltip
);
const
findTimeAgo
=
()
=>
wrapper
.
findAll
(
TimeAgoTooltip
);
const
findDateColumnHeader
=
()
=>
const
findDateColumnHeader
=
()
=>
wrapper
.
find
(
'
[data-testid="incident-management-created-at-sort"]
'
);
wrapper
.
find
(
'
[data-testid="incident-management-created-at-sort"]
'
);
const
findSearch
=
()
=>
wrapper
.
find
(
GlSearchBoxByType
);
const
findSearch
=
()
=>
wrapper
.
find
(
FilteredSearchBar
);
const
findAssingees
=
()
=>
wrapper
.
findAll
(
'
[data-testid="incident-assignees"]
'
);
const
findAssingees
=
()
=>
wrapper
.
findAll
(
'
[data-testid="incident-assignees"]
'
);
const
findCreateIncidentBtn
=
()
=>
wrapper
.
find
(
'
[data-testid="createIncidentBtn"]
'
);
const
findCreateIncidentBtn
=
()
=>
wrapper
.
find
(
'
[data-testid="createIncidentBtn"]
'
);
const
findClosedIcon
=
()
=>
wrapper
.
findAll
(
"
[data-testid='incident-closed']
"
);
const
findClosedIcon
=
()
=>
wrapper
.
findAll
(
"
[data-testid='incident-closed']
"
);
...
@@ -76,6 +80,9 @@ describe('Incidents List', () => {
...
@@ -76,6 +80,9 @@ describe('Incidents List', () => {
issuePath
:
'
/project/isssues
'
,
issuePath
:
'
/project/isssues
'
,
publishedAvailable
:
true
,
publishedAvailable
:
true
,
emptyListSvgPath
,
emptyListSvgPath
,
textQuery
:
''
,
authorUsernamesQuery
:
''
,
assigneeUsernamesQuery
:
''
,
},
},
stubs
:
{
stubs
:
{
GlButton
:
true
,
GlButton
:
true
,
...
@@ -315,7 +322,7 @@ describe('Incidents List', () => {
...
@@ -315,7 +322,7 @@ describe('Incidents List', () => {
});
});
});
});
describe
(
'
Search
'
,
()
=>
{
describe
(
'
Filtered search component
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
mountComponent
({
mountComponent
({
data
:
{
data
:
{
...
@@ -331,15 +338,62 @@ describe('Incidents List', () => {
...
@@ -331,15 +338,62 @@ describe('Incidents List', () => {
});
});
it
(
'
renders the search component for incidents
'
,
()
=>
{
it
(
'
renders the search component for incidents
'
,
()
=>
{
expect
(
findSearch
().
exists
()).
toBe
(
true
);
expect
(
findSearch
().
props
(
'
searchInputPlaceholder
'
)).
toBe
(
'
Search or filter results…
'
);
expect
(
findSearch
().
props
(
'
tokens
'
)).
toEqual
([
{
type
:
'
author_username
'
,
icon
:
'
user
'
,
title
:
'
Author
'
,
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchPath
:
'
/project/path
'
,
fetchAuthors
:
expect
.
any
(
Function
),
},
{
type
:
'
assignee_username
'
,
icon
:
'
user
'
,
title
:
'
Assignees
'
,
unique
:
true
,
symbol
:
'
@
'
,
token
:
AuthorToken
,
operators
:
[{
value
:
'
=
'
,
description
:
'
is
'
,
default
:
'
true
'
}],
fetchPath
:
'
/project/path
'
,
fetchAuthors
:
expect
.
any
(
Function
),
},
]);
expect
(
findSearch
().
props
(
'
recentSearchesStorageKey
'
)).
toBe
(
'
incidents
'
);
});
it
(
'
returns correctly applied filter search values
'
,
async
()
=>
{
const
searchTerm
=
'
foo
'
;
wrapper
.
setData
({
searchTerm
,
});
await
wrapper
.
vm
.
$nextTick
();
expect
(
wrapper
.
vm
.
filteredSearchValue
).
toEqual
([
searchTerm
]);
});
});
it
(
'
sets the `searchTerm` graphql variable
'
,
()
=>
{
it
(
'
updates props tied to getIncidents GraphQL query
'
,
()
=>
{
const
SEARCH_TERM
=
'
Simple Incident
'
;
wrapper
.
vm
.
handleFilterIncidents
(
mockFilters
);
expect
(
wrapper
.
vm
.
authorUsername
).
toBe
(
'
root
'
);
expect
(
wrapper
.
vm
.
assigneeUsernames
).
toEqual
([
'
root2
'
]);
expect
(
wrapper
.
vm
.
searchTerm
).
toBe
(
mockFilters
[
2
].
value
.
data
);
});
it
(
'
updates props `searchTerm` and `authorUsername` with empty values when passed filters param is empty
'
,
()
=>
{
wrapper
.
setData
({
authorUsername
:
'
foo
'
,
searchTerm
:
'
bar
'
,
});
findSearch
().
vm
.
$emit
(
'
input
'
,
SEARCH_TERM
);
wrapper
.
vm
.
handleFilterIncidents
([]
);
expect
(
wrapper
.
vm
.
$data
.
searchTerm
).
toBe
(
SEARCH_TERM
);
expect
(
wrapper
.
vm
.
authorUsername
).
toBe
(
''
);
expect
(
wrapper
.
vm
.
searchTerm
).
toBe
(
''
);
});
});
});
});
...
...
spec/frontend/incidents/mocks/incidents_filter.json
0 → 100644
View file @
4b6bca3a
[
{
"type"
:
"assignee_username"
,
"value"
:
{
"data"
:
"root2"
}
},
{
"type"
:
"author_username"
,
"value"
:
{
"data"
:
"root"
}
},
{
"type"
:
"filtered-search-term"
,
"value"
:
{
"data"
:
"bar"
}
}
]
\ No newline at end of file
spec/graphql/resolvers/issues_resolver_spec.rb
View file @
4b6bca3a
...
@@ -54,10 +54,21 @@ RSpec.describe Resolvers::IssuesResolver do
...
@@ -54,10 +54,21 @@ RSpec.describe Resolvers::IssuesResolver do
expect
(
resolve_issues
(
assignee_id:
IssuableFinder
::
Params
::
FILTER_ANY
)).
to
contain_exactly
(
issue2
)
expect
(
resolve_issues
(
assignee_id:
IssuableFinder
::
Params
::
FILTER_ANY
)).
to
contain_exactly
(
issue2
)
end
end
it
'filters by two assignees'
do
user_2
=
create
(
:user
)
issue2
.
update!
(
assignees:
[
assignee
,
user_2
])
expect
(
resolve_issues
(
assignee_id:
[
assignee
.
id
,
user_2
.
id
])).
to
contain_exactly
(
issue2
)
end
it
'filters by no assignee'
do
it
'filters by no assignee'
do
expect
(
resolve_issues
(
assignee_id:
IssuableFinder
::
Params
::
FILTER_NONE
)).
to
contain_exactly
(
issue1
)
expect
(
resolve_issues
(
assignee_id:
IssuableFinder
::
Params
::
FILTER_NONE
)).
to
contain_exactly
(
issue1
)
end
end
it
'filters by author'
do
expect
(
resolve_issues
(
author_username:
issue1
.
author
.
username
)).
to
contain_exactly
(
issue1
,
issue2
)
end
it
'filters by labels'
do
it
'filters by labels'
do
expect
(
resolve_issues
(
label_name:
[
label1
.
title
])).
to
contain_exactly
(
issue1
,
issue2
)
expect
(
resolve_issues
(
label_name:
[
label1
.
title
])).
to
contain_exactly
(
issue1
,
issue2
)
expect
(
resolve_issues
(
label_name:
[
label1
.
title
,
label2
.
title
])).
to
contain_exactly
(
issue2
)
expect
(
resolve_issues
(
label_name:
[
label1
.
title
,
label2
.
title
])).
to
contain_exactly
(
issue2
)
...
...
spec/helpers/projects/incidents_helper_spec.rb
View file @
4b6bca3a
...
@@ -9,9 +9,16 @@ RSpec.describe Projects::IncidentsHelper do
...
@@ -9,9 +9,16 @@ RSpec.describe Projects::IncidentsHelper do
let
(
:project_path
)
{
project
.
full_path
}
let
(
:project_path
)
{
project
.
full_path
}
let
(
:new_issue_path
)
{
new_project_issue_path
(
project
)
}
let
(
:new_issue_path
)
{
new_project_issue_path
(
project
)
}
let
(
:issue_path
)
{
project_issues_path
(
project
)
}
let
(
:issue_path
)
{
project_issues_path
(
project
)
}
let
(
:params
)
do
{
search:
'search text'
,
author_username:
'root'
,
assignee_username:
'max.power'
}
end
describe
'#incidents_data'
do
describe
'#incidents_data'
do
subject
(
:data
)
{
helper
.
incidents_data
(
project
)
}
subject
(
:data
)
{
helper
.
incidents_data
(
project
,
params
)
}
it
'returns frontend configuration'
do
it
'returns frontend configuration'
do
expect
(
data
).
to
match
(
expect
(
data
).
to
match
(
...
@@ -20,7 +27,10 @@ RSpec.describe Projects::IncidentsHelper do
...
@@ -20,7 +27,10 @@ RSpec.describe Projects::IncidentsHelper do
'incident-template-name'
=>
'incident'
,
'incident-template-name'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'incident-type'
=>
'incident'
,
'issue-path'
=>
issue_path
,
'issue-path'
=>
issue_path
,
'empty-list-svg-path'
=>
match_asset_path
(
'/assets/illustrations/incident-empty-state.svg'
)
'empty-list-svg-path'
=>
match_asset_path
(
'/assets/illustrations/incident-empty-state.svg'
),
'text-query'
:
'search text'
,
'author-usernames-query'
:
'root'
,
'assignee-usernames-query'
:
'max.power'
)
)
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