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
9435047f
Commit
9435047f
authored
Jan 29, 2020
by
Sean Arnold
Committed by
Nick Thomas
Jan 29, 2020
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
WIP Sentry error list
- Add collection type - Add global id to error - Add errors resolver
parent
aba6ac92
Changes
27
Hide whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
1472 additions
and
52 deletions
+1472
-52
app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
...esolvers/error_tracking/sentry_detailed_error_resolver.rb
+8
-1
app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb
...olvers/error_tracking/sentry_error_collection_resolver.rb
+21
-0
app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb
...raphql/resolvers/error_tracking/sentry_errors_resolver.rb
+27
-0
app/graphql/types/error_tracking/sentry_detailed_error_type.rb
...raphql/types/error_tracking/sentry_detailed_error_type.rb
+2
-13
app/graphql/types/error_tracking/sentry_error_collection_type.rb
...phql/types/error_tracking/sentry_error_collection_type.rb
+37
-0
app/graphql/types/error_tracking/sentry_error_type.rb
app/graphql/types/error_tracking/sentry_error_type.rb
+70
-0
app/graphql/types/project_type.rb
app/graphql/types/project_type.rb
+6
-0
app/policies/error_tracking/base_policy.rb
app/policies/error_tracking/base_policy.rb
+1
-1
app/presenters/sentry_error_presenter.rb
app/presenters/sentry_error_presenter.rb
+13
-1
changelogs/unreleased/35897-grapghql-error-tracking-list-errors.yml
.../unreleased/35897-grapghql-error-tracking-list-errors.yml
+5
-0
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+188
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+577
-1
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+37
-0
lib/gitlab/error_tracking/detailed_error.rb
lib/gitlab/error_tracking/detailed_error.rb
+1
-1
lib/gitlab/error_tracking/error.rb
lib/gitlab/error_tracking/error.rb
+5
-0
lib/gitlab/error_tracking/error_collection.rb
lib/gitlab/error_tracking/error_collection.rb
+23
-0
lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb
...raphql/extensions/externally_paginated_array_extension.rb
+12
-0
spec/factories/error_tracking/detailed_error.rb
spec/factories/error_tracking/detailed_error.rb
+7
-28
spec/factories/error_tracking/error.rb
spec/factories/error_tracking/error.rb
+8
-4
spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
...s/error_tracking/sentry_error_collection_resolver_spec.rb
+47
-0
spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
...l/resolvers/error_tracking/sentry_errors_resolver_spec.rb
+103
-0
spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
...types/error_tracking/sentry_error_collection_type_spec.rb
+31
-0
spec/graphql/types/error_tracking/sentry_error_type_spec.rb
spec/graphql/types/error_tracking/sentry_error_type_spec.rb
+31
-0
spec/presenters/sentry_error_presenter_spec.rb
spec/presenters/sentry_error_presenter_spec.rb
+2
-2
spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
...phql/project/error_tracking/sentry_errors_request_spec.rb
+191
-0
spec/support/matchers/graphql_matchers.rb
spec/support/matchers/graphql_matchers.rb
+6
-0
spec/support/shared_examples/error_tracking_shared_examples.rb
...support/shared_examples/error_tracking_shared_examples.rb
+13
-0
No files found.
app/graphql/resolvers/error_tracking/sentry_detailed_error_resolver.rb
View file @
9435047f
...
...
@@ -8,7 +8,6 @@ module Resolvers
description:
'ID of the Sentry issue'
def
resolve
(
**
args
)
project
=
object
current_user
=
context
[
:current_user
]
issue_id
=
GlobalID
.
parse
(
args
[
:id
]).
model_id
...
...
@@ -23,6 +22,14 @@ module Resolvers
issue
end
private
def
project
return
object
.
gitlab_project
if
object
.
respond_to?
(
:gitlab_project
)
object
end
end
end
end
app/graphql/resolvers/error_tracking/sentry_error_collection_resolver.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Resolvers
module
ErrorTracking
class
SentryErrorCollectionResolver
<
BaseResolver
def
resolve
(
**
args
)
project
=
object
service
=
::
ErrorTracking
::
ListIssuesService
.
new
(
project
,
context
[
:current_user
]
)
Gitlab
::
ErrorTracking
::
ErrorCollection
.
new
(
external_url:
service
.
external_url
,
project:
project
)
end
end
end
end
app/graphql/resolvers/error_tracking/sentry_errors_resolver.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Resolvers
module
ErrorTracking
class
SentryErrorsResolver
<
BaseResolver
def
resolve
(
**
args
)
args
[
:cursor
]
=
args
.
delete
(
:after
)
project
=
object
.
project
result
=
::
ErrorTracking
::
ListIssuesService
.
new
(
project
,
context
[
:current_user
],
args
).
execute
next_cursor
=
result
[
:pagination
]
&
.
dig
(
'next'
,
'cursor'
)
previous_cursor
=
result
[
:pagination
]
&
.
dig
(
'previous'
,
'cursor'
)
issues
=
result
[
:issues
]
# ReactiveCache is still fetching data
return
if
issues
.
nil?
Gitlab
::
Graphql
::
ExternallyPaginatedArray
.
new
(
previous_cursor
,
next_cursor
,
*
issues
)
end
end
end
end
app/graphql/types/error_tracking/sentry_detailed_error_type.rb
View file @
9435047f
...
...
@@ -4,8 +4,9 @@ module Types
module
ErrorTracking
class
SentryDetailedErrorType
<
::
Types
::
BaseObject
graphql_name
'SentryDetailedError'
description
'A Sentry error.'
present_using
Sentry
Detailed
ErrorPresenter
present_using
SentryErrorPresenter
authorize
:read_sentry_issue
...
...
@@ -92,18 +93,6 @@ module Types
field
:tags
,
Types
::
ErrorTracking
::
SentryErrorTagsType
,
null:
false
,
description:
'Tags associated with the Sentry Error'
def
first_seen
DateTime
.
parse
(
object
.
first_seen
)
end
def
last_seen
DateTime
.
parse
(
object
.
last_seen
)
end
def
project_id
Gitlab
::
GlobalId
.
build
(
model_name:
'Project'
,
id:
object
.
project_id
).
to_s
end
end
end
end
app/graphql/types/error_tracking/sentry_error_collection_type.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Types
module
ErrorTracking
class
SentryErrorCollectionType
<
::
Types
::
BaseObject
graphql_name
'SentryErrorCollection'
description
'An object containing a collection of Sentry errors, and a detailed error.'
authorize
:read_sentry_issue
field
:errors
,
Types
::
ErrorTracking
::
SentryErrorType
.
connection_type
,
connection:
false
,
null:
true
,
description:
"Collection of Sentry Errors"
,
extensions:
[
Gitlab
::
Graphql
::
Extensions
::
ExternallyPaginatedArrayExtension
],
resolver:
Resolvers
::
ErrorTracking
::
SentryErrorsResolver
do
argument
:search_term
,
String
,
description:
'Search term for the Sentry error.'
,
required:
false
argument
:sort
,
String
,
description:
'Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default.'
,
required:
false
end
field
:detailed_error
,
Types
::
ErrorTracking
::
SentryDetailedErrorType
,
null:
true
,
description:
'Detailed version of a Sentry error on the project'
,
resolver:
Resolvers
::
ErrorTracking
::
SentryDetailedErrorResolver
field
:external_url
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
"External URL for Sentry"
end
end
end
app/graphql/types/error_tracking/sentry_error_type.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Types
module
ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class
SentryErrorType
<
::
Types
::
BaseObject
graphql_name
'SentryError'
description
'A Sentry error. A simplified version of SentryDetailedError.'
present_using
SentryErrorPresenter
field
:id
,
GraphQL
::
ID_TYPE
,
null:
false
,
description:
'ID (global ID) of the error'
field
:sentry_id
,
GraphQL
::
STRING_TYPE
,
method: :id
,
null:
false
,
description:
'ID (Sentry ID) of the error'
field
:first_seen
,
Types
::
TimeType
,
null:
false
,
description:
'Timestamp when the error was first seen'
field
:last_seen
,
Types
::
TimeType
,
null:
false
,
description:
'Timestamp when the error was last seen'
field
:title
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Title of the error'
field
:type
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Type of the error'
field
:user_count
,
GraphQL
::
INT_TYPE
,
null:
false
,
description:
'Count of users affected by the error'
field
:count
,
GraphQL
::
INT_TYPE
,
null:
false
,
description:
'Count of occurrences'
field
:message
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'Sentry metadata message of the error'
field
:culprit
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Culprit of the error'
field
:external_url
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'External URL of the error'
field
:short_id
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Short ID (Sentry ID) of the error'
field
:status
,
Types
::
ErrorTracking
::
SentryErrorStatusEnum
,
null:
false
,
description:
'Status of the error'
field
:frequency
,
[
Types
::
ErrorTracking
::
SentryErrorFrequencyType
],
null:
false
,
description:
'Last 24hr stats of the error'
field
:sentry_project_id
,
GraphQL
::
ID_TYPE
,
method: :project_id
,
null:
false
,
description:
'ID of the project (Sentry project)'
field
:sentry_project_name
,
GraphQL
::
STRING_TYPE
,
method: :project_name
,
null:
false
,
description:
'Name of the project affected by the error'
field
:sentry_project_slug
,
GraphQL
::
STRING_TYPE
,
method: :project_slug
,
null:
false
,
description:
'Slug of the project affected by the error'
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
app/graphql/types/project_type.rb
View file @
9435047f
...
...
@@ -173,6 +173,12 @@ module Types
null:
true
,
description:
'Snippets of the project'
,
resolver:
Resolvers
::
Projects
::
SnippetsResolver
field
:sentry_errors
,
Types
::
ErrorTracking
::
SentryErrorCollectionType
,
null:
true
,
description:
'Paginated collection of Sentry errors on the project'
,
resolver:
Resolvers
::
ErrorTracking
::
SentryErrorCollectionResolver
end
end
...
...
app/policies/error_tracking/
detailed_error
_policy.rb
→
app/policies/error_tracking/
base
_policy.rb
View file @
9435047f
# frozen_string_literal: true
module
ErrorTracking
class
DetailedErrorPolicy
<
BasePolicy
class
BasePolicy
<
::
BasePolicy
delegate
{
@subject
.
gitlab_project
}
end
end
app/presenters/sentry_
detailed_
error_presenter.rb
→
app/presenters/sentry_error_presenter.rb
View file @
9435047f
# frozen_string_literal: true
class
Sentry
Detailed
ErrorPresenter
<
Gitlab
::
View
::
Presenter
::
Delegated
class
SentryErrorPresenter
<
Gitlab
::
View
::
Presenter
::
Delegated
presents
:error
FrequencyStruct
=
Struct
.
new
(
:time
,
:count
,
keyword_init:
true
)
def
first_seen
DateTime
.
parse
(
error
.
first_seen
)
end
def
last_seen
DateTime
.
parse
(
error
.
last_seen
)
end
def
project_id
Gitlab
::
GlobalId
.
build
(
model_name:
'Project'
,
id:
error
.
project_id
).
to_s
end
def
frequency
utc_offset
=
Time
.
zone_offset
(
'UTC'
)
...
...
changelogs/unreleased/35897-grapghql-error-tracking-list-errors.yml
0 → 100644
View file @
9435047f
---
title
:
Add querying of Sentry errors to Graphql
merge_request
:
21802
author
:
type
:
added
doc/api/graphql/reference/gitlab_schema.graphql
View file @
9435047f
...
...
@@ -5453,6 +5453,11 @@ type Project {
id
:
ID
!
):
SentryDetailedError
"""
Paginated
collection
of
Sentry
errors
on
the
project
"""
sentryErrors
:
SentryErrorCollection
"""
E
-
mail
address
of
the
service
desk
.
"""
...
...
@@ -6054,6 +6059,9 @@ type RootStorageStatistics {
wikiSize
:
Int
!
}
"""
A Sentry error.
"""
type
SentryDetailedError
{
"""
Count
of
occurrences
...
...
@@ -6186,6 +6194,186 @@ type SentryDetailedError {
userCount
:
Int
!
}
"""
A Sentry error. A simplified version of SentryDetailedError.
"""
type
SentryError
{
"""
Count
of
occurrences
"""
count
:
Int
!
"""
Culprit
of
the
error
"""
culprit
:
String
!
"""
External
URL
of
the
error
"""
externalUrl
:
String
!
"""
Timestamp
when
the
error
was
first
seen
"""
firstSeen
:
Time
!
"""
Last
24
hr
stats
of
the
error
"""
frequency
:
[
SentryErrorFrequency
!]!
"""
ID
(
global
ID
)
of
the
error
"""
id
:
ID
!
"""
Timestamp
when
the
error
was
last
seen
"""
lastSeen
:
Time
!
"""
Sentry
metadata
message
of
the
error
"""
message
:
String
"""
ID
(
Sentry
ID
)
of
the
error
"""
sentryId
:
String
!
"""
ID
of
the
project
(
Sentry
project
)
"""
sentryProjectId
:
ID
!
"""
Name
of
the
project
affected
by
the
error
"""
sentryProjectName
:
String
!
"""
Slug
of
the
project
affected
by
the
error
"""
sentryProjectSlug
:
String
!
"""
Short
ID
(
Sentry
ID
)
of
the
error
"""
shortId
:
String
!
"""
Status
of
the
error
"""
status
:
SentryErrorStatus
!
"""
Title
of
the
error
"""
title
:
String
!
"""
Type
of
the
error
"""
type
:
String
!
"""
Count
of
users
affected
by
the
error
"""
userCount
:
Int
!
}
"""
An object containing a collection of Sentry errors, and a detailed error.
"""
type
SentryErrorCollection
{
"""
Detailed
version
of
a
Sentry
error
on
the
project
"""
detailedError
(
"""
ID
of
the
Sentry
issue
"""
id
:
ID
!
):
SentryDetailedError
"""
Collection
of
Sentry
Errors
"""
errors
(
"""
Returns
the
elements
in
the
list
that
come
after
the
specified
cursor
.
"""
after
:
String
"""
Returns
the
elements
in
the
list
that
come
before
the
specified
cursor
.
"""
before
:
String
"""
Returns
the
first
_n_
elements
from
the
list
.
"""
first
:
Int
"""
Returns
the
last
_n_
elements
from
the
list
.
"""
last
:
Int
"""
Search
term
for
the
Sentry
error
.
"""
searchTerm
:
String
"""
Attribute
to
sort
on
.
Options
are
frequency
,
first_seen
,
last_seen
.
last_seen
is
default
.
"""
sort
:
String
):
SentryErrorConnection
"""
External
URL
for
Sentry
"""
externalUrl
:
String
}
"""
The connection type for SentryError.
"""
type
SentryErrorConnection
{
"""
A
list
of
edges
.
"""
edges
:
[
SentryErrorEdge
]
"""
A
list
of
nodes
.
"""
nodes
:
[
SentryError
]
"""
Information
to
aid
in
pagination
.
"""
pageInfo
:
PageInfo
!
}
"""
An edge in a connection.
"""
type
SentryErrorEdge
{
"""
A
cursor
for
use
in
pagination
.
"""
cursor
:
String
!
"""
The
item
at
the
end
of
the
edge
.
"""
node
:
SentryError
}
type
SentryErrorFrequency
{
"""
Count
of
errors
received
since
the
previously
recorded
time
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
9435047f
...
...
@@ -1433,6 +1433,20 @@
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"sentryErrors"
,
"description"
:
"Paginated collection of Sentry errors on the project"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorCollection"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"serviceDeskAddress"
,
"description"
:
"E-mail address of the service desk."
,
...
...
@@ -16708,7 +16722,7 @@
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryDetailedError"
,
"description"
:
null
,
"description"
:
"A Sentry error."
,
"fields"
:
[
{
"name"
:
"count"
,
...
...
@@ -17408,6 +17422,568 @@
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorCollection"
,
"description"
:
"An object containing a collection of Sentry errors, and a detailed error."
,
"fields"
:
[
{
"name"
:
"detailedError"
,
"description"
:
"Detailed version of a Sentry error on the project"
,
"args"
:
[
{
"name"
:
"id"
,
"description"
:
"ID of the Sentry issue"
,
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"ID"
,
"ofType"
:
null
}
},
"defaultValue"
:
null
}
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryDetailedError"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"errors"
,
"description"
:
"Collection of Sentry Errors"
,
"args"
:
[
{
"name"
:
"after"
,
"description"
:
"Returns the elements in the list that come after the specified cursor."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"before"
,
"description"
:
"Returns the elements in the list that come before the specified cursor."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"first"
,
"description"
:
"Returns the first _n_ elements from the list."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Int"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"last"
,
"description"
:
"Returns the last _n_ elements from the list."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Int"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"searchTerm"
,
"description"
:
"Search term for the Sentry error."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
},
{
"name"
:
"sort"
,
"description"
:
"Attribute to sort on. Options are frequency, first_seen, last_seen. last_seen is default."
,
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"defaultValue"
:
null
}
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorConnection"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"externalUrl"
,
"description"
:
"External URL for Sentry"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorConnection"
,
"description"
:
"The connection type for SentryError."
,
"fields"
:
[
{
"name"
:
"edges"
,
"description"
:
"A list of edges."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorEdge"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"nodes"
,
"description"
:
"A list of nodes."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryError"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"pageInfo"
,
"description"
:
"Information to aid in pagination."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"PageInfo"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorEdge"
,
"description"
:
"An edge in a connection."
,
"fields"
:
[
{
"name"
:
"cursor"
,
"description"
:
"A cursor for use in pagination."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"node"
,
"description"
:
"The item at the end of the edge."
,
"args"
:
[
],
"type"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryError"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryError"
,
"description"
:
"A Sentry error. A simplified version of SentryDetailedError."
,
"fields"
:
[
{
"name"
:
"count"
,
"description"
:
"Count of occurrences"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Int"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"culprit"
,
"description"
:
"Culprit of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"externalUrl"
,
"description"
:
"External URL of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"firstSeen"
,
"description"
:
"Timestamp when the error was first seen"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Time"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"frequency"
,
"description"
:
"Last 24hr stats of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorFrequency"
,
"ofType"
:
null
}
}
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"id"
,
"description"
:
"ID (global ID) of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"ID"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"lastSeen"
,
"description"
:
"Timestamp when the error was last seen"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Time"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"message"
,
"description"
:
"Sentry metadata message of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"sentryId"
,
"description"
:
"ID (Sentry ID) of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"sentryProjectId"
,
"description"
:
"ID of the project (Sentry project)"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"ID"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"sentryProjectName"
,
"description"
:
"Name of the project affected by the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"sentryProjectSlug"
,
"description"
:
"Slug of the project affected by the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"shortId"
,
"description"
:
"Short ID (Sentry ID) of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"status"
,
"description"
:
"Status of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"ENUM"
,
"name"
:
"SentryErrorStatus"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"title"
,
"description"
:
"Title of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"type"
,
"description"
:
"Type of the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"userCount"
,
"description"
:
"Count of users affected by the error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"Int"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"Metadata"
,
...
...
doc/api/graphql/reference/index.md
View file @
9435047f
...
...
@@ -815,6 +815,7 @@ Information about pagination in a connection.
|
`repository`
| Repository | Git repository of the project |
|
`requestAccessEnabled`
| Boolean | Indicates if users can request member access to the project |
|
`sentryDetailedError`
| SentryDetailedError | Detailed version of a Sentry error on the project |
|
`sentryErrors`
| SentryErrorCollection | Paginated collection of Sentry errors on the project |
|
`serviceDeskAddress`
| String | E-mail address of the service desk. |
|
`serviceDeskEnabled`
| Boolean | Indicates if the project has service desk enabled. |
|
`sharedRunnersEnabled`
| Boolean | Indicates if shared runners are enabled on the project |
...
...
@@ -919,6 +920,8 @@ Autogenerated return type of RemoveAwardEmoji
## SentryDetailedError
A Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`count`
| Int! | Count of occurrences |
...
...
@@ -948,6 +951,40 @@ Autogenerated return type of RemoveAwardEmoji
|
`type`
| String! | Type of the error |
|
`userCount`
| Int! | Count of users affected by the error |
## SentryError
A Sentry error. A simplified version of SentryDetailedError.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`count`
| Int! | Count of occurrences |
|
`culprit`
| String! | Culprit of the error |
|
`externalUrl`
| String! | External URL of the error |
|
`firstSeen`
| Time! | Timestamp when the error was first seen |
|
`frequency`
| SentryErrorFrequency! => Array | Last 24hr stats of the error |
|
`id`
| ID! | ID (global ID) of the error |
|
`lastSeen`
| Time! | Timestamp when the error was last seen |
|
`message`
| String | Sentry metadata message of the error |
|
`sentryId`
| String! | ID (Sentry ID) of the error |
|
`sentryProjectId`
| ID! | ID of the project (Sentry project) |
|
`sentryProjectName`
| String! | Name of the project affected by the error |
|
`sentryProjectSlug`
| String! | Slug of the project affected by the error |
|
`shortId`
| String! | Short ID (Sentry ID) of the error |
|
`status`
| SentryErrorStatus! | Status of the error |
|
`title`
| String! | Title of the error |
|
`type`
| String! | Type of the error |
|
`userCount`
| Int! | Count of users affected by the error |
## SentryErrorCollection
An object containing a collection of Sentry errors, and a detailed error.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`detailedError`
| SentryDetailedError | Detailed version of a Sentry error on the project |
|
`errors`
| SentryErrorConnection | Collection of Sentry Errors |
|
`externalUrl`
| String | External URL for Sentry |
## SentryErrorFrequency
| Name | Type | Description |
...
...
lib/gitlab/error_tracking/detailed_error.rb
View file @
9435047f
...
...
@@ -35,7 +35,7 @@ module Gitlab
:user_count
def
self
.
declarative_policy_class
'ErrorTracking::
DetailedError
Policy'
'ErrorTracking::
Base
Policy'
end
end
end
...
...
lib/gitlab/error_tracking/error.rb
View file @
9435047f
...
...
@@ -4,11 +4,16 @@ module Gitlab
module
ErrorTracking
class
Error
include
ActiveModel
::
Model
include
GlobalID
::
Identification
attr_accessor
:id
,
:title
,
:type
,
:user_count
,
:count
,
:first_seen
,
:last_seen
,
:message
,
:culprit
,
:external_url
,
:project_id
,
:project_name
,
:project_slug
,
:short_id
,
:status
,
:frequency
def
self
.
declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
lib/gitlab/error_tracking/error_collection.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Gitlab
module
ErrorTracking
class
ErrorCollection
include
GlobalID
::
Identification
attr_accessor
:issues
,
:external_url
,
:project
alias_attribute
:gitlab_project
,
:project
def
initialize
(
project
:,
external_url:
nil
,
issues:
[])
@project
=
project
@external_url
=
external_url
@issues
=
issues
end
def
self
.
declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
lib/gitlab/graphql/extensions/externally_paginated_array_extension.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
module
Gitlab
module
Graphql
module
Extensions
class
ExternallyPaginatedArrayExtension
<
GraphQL
::
Schema
::
Field
::
ConnectionExtension
def
resolve
(
object
:,
arguments
:,
context
:)
yield
(
object
,
arguments
)
end
end
end
end
end
spec/factories/error_tracking/detailed_error.rb
View file @
9435047f
# frozen_string_literal: true
FactoryBot
.
define
do
factory
:detailed_error_tracking_error
,
class:
'Gitlab::ErrorTracking::DetailedError'
do
id
{
'1'
}
title
{
'title'
}
type
{
'error'
}
user_count
{
1
}
count
{
2
}
first_seen
{
Time
.
now
.
iso8601
}
last_seen
{
Time
.
now
.
iso8601
}
message
{
'message'
}
culprit
{
'culprit'
}
external_url
{
'http://example.com/id'
}
factory
:detailed_error_tracking_error
,
parent: :error_tracking_error
,
class:
'Gitlab::ErrorTracking::DetailedError'
do
gitlab_issue
{
'http://gitlab.example.com/issues/1'
}
external_base_url
{
'http://example.com'
}
project_id
{
'project1
'
}
project_name
{
'project name
'
}
project_slug
{
'project_name
'
}
short_id
{
'ID
'
}
status
{
'unresolved
'
}
first_release_last_commit
{
'68c914da9
'
}
last_release_last_commit
{
'9ad419c86
'
}
first_release_short_version
{
'abc123
'
}
last_release_short_version
{
'abc123
'
}
first_release_version
{
'12345678
'
}
tags
do
{
level:
'error'
,
logger:
'rails'
}
end
frequency
do
[
[
Time
.
now
.
to_i
,
10
]
]
end
gitlab_issue
{
'http://gitlab.example.com/issues/1'
}
first_release_last_commit
{
'68c914da9'
}
last_release_last_commit
{
'9ad419c86'
}
first_release_short_version
{
'abc123'
}
last_release_short_version
{
'abc123'
}
first_release_version
{
'12345678'
}
skip_create
end
end
spec/factories/error_tracking/error.rb
View file @
9435047f
...
...
@@ -2,13 +2,13 @@
FactoryBot
.
define
do
factory
:error_tracking_error
,
class:
'Gitlab::ErrorTracking::Error'
do
id
{
'
id
'
}
id
{
'
1
'
}
title
{
'title'
}
type
{
'error'
}
user_count
{
1
}
count
{
2
}
first_seen
{
Time
.
now
}
last_seen
{
Time
.
now
}
first_seen
{
Time
.
now
.
iso8601
}
last_seen
{
Time
.
now
.
iso8601
}
message
{
'message'
}
culprit
{
'culprit'
}
external_url
{
'http://example.com/id'
}
...
...
@@ -17,7 +17,11 @@ FactoryBot.define do
project_slug
{
'project_name'
}
short_id
{
'ID'
}
status
{
'unresolved'
}
frequency
{
[]
}
frequency
do
[
[
Time
.
now
.
to_i
,
10
]
]
end
skip_create
end
...
...
spec/graphql/resolvers/error_tracking/sentry_error_collection_resolver_spec.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
require
'spec_helper'
describe
Resolvers
::
ErrorTracking
::
SentryErrorCollectionResolver
do
include
GraphqlHelpers
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let
(
:list_issues_service
)
{
spy
(
'ErrorTracking::ListIssuesService'
)
}
before
do
project
.
add_developer
(
current_user
)
allow
(
ErrorTracking
::
ListIssuesService
)
.
to
receive
(
:new
)
.
and_return
list_issues_service
end
describe
'#resolve'
do
it
'returns an error collection object'
do
expect
(
resolve_error_collection
).
to
be_a
Gitlab
::
ErrorTracking
::
ErrorCollection
end
it
'provides the service url'
do
fake_url
=
'http://test.com'
expect
(
list_issues_service
)
.
to
receive
(
:external_url
)
.
and_return
(
fake_url
)
result
=
resolve_error_collection
expect
(
result
.
external_url
).
to
eq
fake_url
end
it
'provides the project'
do
expect
(
resolve_error_collection
.
project
).
to
eq
project
end
end
private
def
resolve_error_collection
(
context
=
{
current_user:
current_user
})
resolve
(
described_class
,
obj:
project
,
args:
{},
ctx:
context
)
end
end
spec/graphql/resolvers/error_tracking/sentry_errors_resolver_spec.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
require
'spec_helper'
describe
Resolvers
::
ErrorTracking
::
SentryErrorsResolver
do
include
GraphqlHelpers
let_it_be
(
:project
)
{
create
(
:project
)
}
let_it_be
(
:current_user
)
{
create
(
:user
)
}
let_it_be
(
:error_collection
)
{
Gitlab
::
ErrorTracking
::
ErrorCollection
.
new
(
project:
project
)
}
let
(
:list_issues_service
)
{
spy
(
'ErrorTracking::ListIssuesService'
)
}
let
(
:issues
)
{
nil
}
let
(
:pagination
)
{
nil
}
describe
'#resolve'
do
context
'insufficient user permission'
do
let
(
:user
)
{
create
(
:user
)
}
it
'returns nil'
do
context
=
{
current_user:
user
}
expect
(
resolve_errors
({},
context
)).
to
eq
nil
end
end
context
'user with permission'
do
before
do
project
.
add_developer
(
current_user
)
allow
(
ErrorTracking
::
ListIssuesService
)
.
to
receive
(
:new
)
.
and_return
list_issues_service
end
context
'when after arg given'
do
let
(
:after
)
{
"1576029072000:0:0"
}
it
'gives the cursor arg'
do
expect
(
ErrorTracking
::
ListIssuesService
)
.
to
receive
(
:new
)
.
with
(
project
,
current_user
,
{
cursor:
after
})
.
and_return
list_issues_service
resolve_errors
({
after:
after
})
end
end
context
'when no issues fetched'
do
before
do
allow
(
list_issues_service
)
.
to
receive
(
:execute
)
.
and_return
(
issues:
nil
)
end
it
'returns nil'
do
expect
(
resolve_errors
).
to
eq
nil
end
end
context
'when issues returned'
do
let
(
:issues
)
{
[
:issue_1
,
:issue_2
]
}
let
(
:pagination
)
do
{
'next'
=>
{
'cursor'
=>
'next'
},
'previous'
=>
{
'cursor'
=>
'prev'
}
}
end
before
do
allow
(
list_issues_service
)
.
to
receive
(
:execute
)
.
and_return
(
issues:
issues
,
pagination:
pagination
)
end
it
'sets the issues'
do
expect
(
resolve_errors
).
to
contain_exactly
(
*
issues
)
end
it
'sets the pagination variables'
do
result
=
resolve_errors
expect
(
result
.
next_cursor
).
to
eq
'next'
expect
(
result
.
previous_cursor
).
to
eq
'prev'
end
it
'returns an externally paginated array'
do
expect
(
resolve_errors
).
to
be_a
Gitlab
::
Graphql
::
ExternallyPaginatedArray
end
end
end
end
private
def
resolve_errors
(
args
=
{},
context
=
{
current_user:
current_user
})
resolve
(
described_class
,
obj:
error_collection
,
args:
args
,
ctx:
context
)
end
end
spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
require
'spec_helper'
describe
GitlabSchema
.
types
[
'SentryErrorCollection'
]
do
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'SentryErrorCollection'
)
}
it
{
expect
(
described_class
).
to
require_graphql_authorizations
(
:read_sentry_issue
)
}
it
'exposes the expected fields'
do
expected_fields
=
%i[
errors
detailed_error
external_url
]
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
end
describe
'errors field'
do
subject
{
described_class
.
fields
[
'errors'
]
}
it
'returns errors'
do
aggregate_failures
'testing the correct types are returned'
do
is_expected
.
to
have_graphql_type
(
Types
::
ErrorTracking
::
SentryErrorType
.
connection_type
)
is_expected
.
to
have_graphql_extension
(
Gitlab
::
Graphql
::
Extensions
::
ExternallyPaginatedArrayExtension
)
is_expected
.
to
have_graphql_resolver
(
Resolvers
::
ErrorTracking
::
SentryErrorsResolver
)
end
end
end
end
spec/graphql/types/error_tracking/sentry_error_type_spec.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
require
'spec_helper'
describe
GitlabSchema
.
types
[
'SentryError'
]
do
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'SentryError'
)
}
it
'exposes the expected fields'
do
expected_fields
=
%i[
id
sentryId
title
type
userCount
count
firstSeen
lastSeen
message
culprit
externalUrl
sentryProjectId
sentryProjectName
sentryProjectSlug
shortId
status
frequency
]
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
end
end
spec/presenters/sentry_
detailed_
error_presenter_spec.rb
→
spec/presenters/sentry_error_presenter_spec.rb
View file @
9435047f
...
...
@@ -2,7 +2,7 @@
require
'spec_helper'
describe
Sentry
Detailed
ErrorPresenter
do
describe
SentryErrorPresenter
do
let
(
:error
)
{
build
(
:detailed_error_tracking_error
)
}
let
(
:presenter
)
{
described_class
.
new
(
error
)
}
...
...
@@ -10,7 +10,7 @@ describe SentryDetailedErrorPresenter do
subject
{
presenter
.
frequency
}
it
'returns an array of frequency structs'
do
expect
(
subject
).
to
include
(
a_kind_of
(
Sentry
Detailed
ErrorPresenter
::
FrequencyStruct
))
expect
(
subject
).
to
include
(
a_kind_of
(
SentryErrorPresenter
::
FrequencyStruct
))
end
it
'converts the times into UTC time objects'
do
...
...
spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
require
'spec_helper'
describe
'sentry errors requests'
do
include
GraphqlHelpers
let_it_be
(
:project
)
{
create
(
:project
,
:repository
)
}
let_it_be
(
:project_setting
)
{
create
(
:project_error_tracking_setting
,
project:
project
)
}
let_it_be
(
:current_user
)
{
project
.
owner
}
let
(
:query
)
do
graphql_query_for
(
'project'
,
{
'fullPath'
=>
project
.
full_path
},
query_graphql_field
(
'sentryErrors'
,
{},
fields
)
)
end
describe
'getting a detailed sentry error'
do
let_it_be
(
:sentry_detailed_error
)
{
build
(
:detailed_error_tracking_error
)
}
let
(
:sentry_gid
)
{
sentry_detailed_error
.
to_global_id
.
to_s
}
let
(
:detailed_fields
)
do
all_graphql_fields_for
(
'SentryDetailedError'
.
classify
)
end
let
(
:fields
)
do
query_graphql_field
(
'detailedError'
,
{
id:
sentry_gid
},
detailed_fields
)
end
let
(
:error_data
)
{
graphql_data
.
dig
(
'project'
,
'sentryErrors'
,
'detailedError'
)
}
it_behaves_like
'a working graphql query'
do
before
do
post_graphql
(
query
,
current_user:
current_user
)
end
end
context
'when data is loading via reactive cache'
do
before
do
post_graphql
(
query
,
current_user:
current_user
)
end
it
"is expected to return an empty error"
do
expect
(
error_data
).
to
eq
nil
end
end
context
'reactive cache returns data'
do
before
do
allow_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
and_return
({
issue:
sentry_detailed_error
})
post_graphql
(
query
,
current_user:
current_user
)
end
let
(
:sentry_error
)
{
sentry_detailed_error
}
let
(
:error
)
{
error_data
}
it_behaves_like
'setting sentry error data'
it
'is expected to return the frequency correctly'
do
aggregate_failures
'it returns the frequency correctly'
do
expect
(
error_data
[
'frequency'
].
count
).
to
eql
sentry_detailed_error
.
frequency
.
count
first_frequency
=
error_data
[
'frequency'
].
first
expect
(
Time
.
parse
(
first_frequency
[
'time'
])).
to
eql
Time
.
at
(
sentry_detailed_error
.
frequency
[
0
][
0
],
in:
0
)
expect
(
first_frequency
[
'count'
]).
to
eql
sentry_detailed_error
.
frequency
[
0
][
1
]
end
end
context
'user does not have permission'
do
let
(
:current_user
)
{
create
(
:user
)
}
it
"is expected to return an empty error"
do
expect
(
error_data
).
to
eq
nil
end
end
end
context
'sentry api returns an error'
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
and_return
({
error:
'error message'
})
post_graphql
(
query
,
current_user:
current_user
)
end
it
'is expected to handle the error and return nil'
do
expect
(
error_data
).
to
eq
nil
end
end
end
describe
'getting an errors list'
do
let_it_be
(
:sentry_error
)
{
build
(
:error_tracking_error
)
}
let_it_be
(
:pagination
)
do
{
'next'
=>
{
'cursor'
=>
'2222'
},
'previous'
=>
{
'cursor'
=>
'1111'
}
}
end
let
(
:fields
)
do
<<~
QUERY
errors {
nodes {
#{
all_graphql_fields_for
(
'SentryError'
.
classify
)
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
}
QUERY
end
let
(
:error_data
)
{
graphql_data
.
dig
(
'project'
,
'sentryErrors'
,
'errors'
,
'nodes'
)
}
let
(
:pagination_data
)
{
graphql_data
.
dig
(
'project'
,
'sentryErrors'
,
'errors'
,
'pageInfo'
)
}
it_behaves_like
'a working graphql query'
do
before
do
post_graphql
(
query
,
current_user:
current_user
)
end
end
context
'when data is loading via reactive cache'
do
before
do
post_graphql
(
query
,
current_user:
current_user
)
end
it
"is expected to return nil"
do
expect
(
error_data
).
to
eq
nil
end
end
context
'reactive cache returns data'
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:list_sentry_issues
)
.
and_return
({
issues:
[
sentry_error
],
pagination:
pagination
})
post_graphql
(
query
,
current_user:
current_user
)
end
let
(
:error
)
{
error_data
.
first
}
it
'is expected to return an array of data'
do
expect
(
error_data
).
to
be_a
Array
expect
(
error_data
.
count
).
to
eq
1
end
it_behaves_like
'setting sentry error data'
it
'sets the pagination correctly'
do
expect
(
pagination_data
[
'startCursor'
]).
to
eq
(
pagination
[
'previous'
][
'cursor'
])
expect
(
pagination_data
[
'endCursor'
]).
to
eq
(
pagination
[
'next'
][
'cursor'
])
end
it
'is expected to return the frequency correctly'
do
aggregate_failures
'it returns the frequency correctly'
do
error
=
error_data
.
first
expect
(
error
[
'frequency'
].
count
).
to
eql
sentry_error
.
frequency
.
count
first_frequency
=
error
[
'frequency'
].
first
expect
(
Time
.
parse
(
first_frequency
[
'time'
])).
to
eql
Time
.
at
(
sentry_error
.
frequency
[
0
][
0
],
in:
0
)
expect
(
first_frequency
[
'count'
]).
to
eql
sentry_error
.
frequency
[
0
][
1
]
end
end
end
context
"sentry api itself errors out"
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:list_sentry_issues
)
.
and_return
({
error:
'error message'
})
post_graphql
(
query
,
current_user:
current_user
)
end
it
'is expected to handle the error and return nil'
do
expect
(
error_data
).
to
eq
nil
end
end
end
end
spec/support/matchers/graphql_matchers.rb
View file @
9435047f
...
...
@@ -108,6 +108,12 @@ RSpec::Matchers.define :have_graphql_resolver do |expected|
end
end
RSpec
::
Matchers
.
define
:have_graphql_extension
do
|
expected
|
match
do
|
field
|
expect
(
field
.
metadata
[
:type_class
].
extensions
).
to
include
(
expected
)
end
end
RSpec
::
Matchers
.
define
:expose_permissions_using
do
|
expected
|
match
do
|
type
|
permission_field
=
type
.
fields
[
'userPermissions'
]
...
...
spec/support/shared_examples/error_tracking_shared_examples.rb
0 → 100644
View file @
9435047f
# frozen_string_literal: true
RSpec
.
shared_examples
'setting sentry error data'
do
it
'sets the sentry error data correctly'
do
aggregate_failures
'testing the sentry error is correct'
do
expect
(
error
[
'id'
]).
to
eql
sentry_error
.
to_global_id
.
to_s
expect
(
error
[
'sentryId'
]).
to
eql
sentry_error
.
id
.
to_s
expect
(
error
[
'status'
]).
to
eql
sentry_error
.
status
.
upcase
expect
(
error
[
'firstSeen'
]).
to
eql
sentry_error
.
first_seen
expect
(
error
[
'lastSeen'
]).
to
eql
sentry_error
.
last_seen
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