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
d5d3c035
Commit
d5d3c035
authored
Jan 31, 2020
by
GitLab Bot
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add latest changes from gitlab-org/gitlab@master
parent
0434f38e
Changes
51
Hide whitespace changes
Inline
Side-by-side
Showing
51 changed files
with
1136 additions
and
304 deletions
+1136
-304
Gemfile.lock
Gemfile.lock
+2
-2
app/assets/javascripts/error_tracking/components/constants.js
...assets/javascripts/error_tracking/components/constants.js
+6
-0
app/assets/javascripts/error_tracking/components/error_details.vue
...s/javascripts/error_tracking/components/error_details.vue
+32
-12
app/assets/javascripts/error_tracking/details.js
app/assets/javascripts/error_tracking/details.js
+0
-2
app/assets/javascripts/error_tracking/queries/details.query.graphql
.../javascripts/error_tracking/queries/details.query.graphql
+1
-0
app/assets/javascripts/error_tracking/store/actions.js
app/assets/javascripts/error_tracking/store/actions.js
+26
-9
app/assets/javascripts/error_tracking/store/details/state.js
app/assets/javascripts/error_tracking/store/details/state.js
+1
-0
app/assets/javascripts/error_tracking/store/mutation_types.js
...assets/javascripts/error_tracking/store/mutation_types.js
+1
-0
app/assets/javascripts/error_tracking/store/mutations.js
app/assets/javascripts/error_tracking/store/mutations.js
+3
-0
app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb
...lvers/error_tracking/sentry_error_stack_trace_resolver.rb
+35
-0
app/graphql/types/error_tracking/sentry_error_collection_type.rb
...phql/types/error_tracking/sentry_error_collection_type.rb
+4
-0
app/graphql/types/error_tracking/sentry_error_stack_trace_context_type.rb
...s/error_tracking/sentry_error_stack_trace_context_type.rb
+29
-0
app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb
...pes/error_tracking/sentry_error_stack_trace_entry_type.rb
+48
-0
app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb
...hql/types/error_tracking/sentry_error_stack_trace_type.rb
+22
-0
app/helpers/projects/error_tracking_helper.rb
app/helpers/projects/error_tracking_helper.rb
+0
-1
app/views/explore/projects/_nav.html.haml
app/views/explore/projects/_nav.html.haml
+6
-6
changelogs/unreleased/19165-explore-projects-default-to-all.yml
...logs/unreleased/19165-explore-projects-default-to-all.yml
+5
-0
changelogs/unreleased/196881-reverse-actions-for-status-update.yml
...s/unreleased/196881-reverse-actions-for-status-update.yml
+5
-0
changelogs/unreleased/35896-graphql-error-stack-trace.yml
changelogs/unreleased/35896-graphql-error-stack-trace.yml
+5
-0
changelogs/unreleased/refactoring-entities-file-8.yml
changelogs/unreleased/refactoring-entities-file-8.yml
+5
-0
config/mail_room.yml
config/mail_room.yml
+3
-5
config/routes/explore.rb
config/routes/explore.rb
+1
-1
config/sidekiq_queues.yml
config/sidekiq_queues.yml
+2
-0
doc/administration/high_availability/README.md
doc/administration/high_availability/README.md
+1
-1
doc/api/graphql/reference/gitlab_schema.graphql
doc/api/graphql/reference/gitlab_schema.graphql
+75
-0
doc/api/graphql/reference/gitlab_schema.json
doc/api/graphql/reference/gitlab_schema.json
+242
-0
doc/api/graphql/reference/index.md
doc/api/graphql/reference/index.md
+32
-0
lib/api/entities.rb
lib/api/entities.rb
+0
-69
lib/api/entities/basic_ref.rb
lib/api/entities/basic_ref.rb
+9
-0
lib/api/entities/branch.rb
lib/api/entities/branch.rb
+41
-0
lib/api/entities/personal_snippet.rb
lib/api/entities/personal_snippet.rb
+11
-0
lib/api/entities/project_snippet.rb
lib/api/entities/project_snippet.rb
+8
-0
lib/api/entities/snippet.rb
lib/api/entities/snippet.rb
+15
-0
lib/api/entities/tree_object.rb
lib/api/entities/tree_object.rb
+15
-0
lib/gitlab/error_tracking/error_event.rb
lib/gitlab/error_tracking/error_event.rb
+5
-1
lib/gitlab/mail_room.rb
lib/gitlab/mail_room.rb
+42
-20
locale/gitlab.pot
locale/gitlab.pot
+6
-0
qa/qa/tools/delete_subgroups.rb
qa/qa/tools/delete_subgroups.rb
+15
-15
spec/config/mail_room_spec.rb
spec/config/mail_room_spec.rb
+20
-28
spec/features/dashboard/shortcuts_spec.rb
spec/features/dashboard/shortcuts_spec.rb
+1
-1
spec/fixtures/config/mail_room_disabled.yml
spec/fixtures/config/mail_room_disabled.yml
+11
-0
spec/fixtures/config/mail_room_enabled.yml
spec/fixtures/config/mail_room_enabled.yml
+11
-0
spec/frontend/error_tracking/components/error_details_spec.js
.../frontend/error_tracking/components/error_details_spec.js
+98
-1
spec/frontend/error_tracking/store/actions_spec.js
spec/frontend/error_tracking/store/actions_spec.js
+32
-32
spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
...types/error_tracking/sentry_error_collection_type_spec.rb
+1
-0
spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
...rror_tracking/sentry_error_stack_trace_entry_type_spec.rb
+19
-0
spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
...ypes/error_tracking/sentry_error_stack_trace_type_spec.rb
+19
-0
spec/helpers/projects/error_tracking_helper_spec.rb
spec/helpers/projects/error_tracking_helper_spec.rb
+0
-5
spec/lib/gitlab/mail_room/mail_room_spec.rb
spec/lib/gitlab/mail_room/mail_room_spec.rb
+59
-75
spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
...phql/project/error_tracking/sentry_errors_request_spec.rb
+78
-13
spec/support/shared_examples/error_tracking_shared_examples.rb
...support/shared_examples/error_tracking_shared_examples.rb
+28
-5
No files found.
Gemfile.lock
View file @
d5d3c035
...
@@ -184,7 +184,7 @@ GEM
...
@@ -184,7 +184,7 @@ GEM
unicode_utils (~> 1.4)
unicode_utils (~> 1.4)
crack (0.4.3)
crack (0.4.3)
safe_yaml (~> 1.0.0)
safe_yaml (~> 1.0.0)
crass (1.0.
5
)
crass (1.0.
6
)
creole (0.5.0)
creole (0.5.0)
css_parser (1.7.0)
css_parser (1.7.0)
addressable
addressable
...
@@ -526,7 +526,7 @@ GEM
...
@@ -526,7 +526,7 @@ GEM
mime-types (~> 3.0)
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
httpclient (2.8.3)
i18n (1.
7.0
)
i18n (1.
8.2
)
concurrent-ruby (~> 1.0)
concurrent-ruby (~> 1.0)
i18n_data (0.8.0)
i18n_data (0.8.0)
icalendar (2.4.1)
icalendar (2.4.1)
...
...
app/assets/javascripts/error_tracking/components/constants.js
View file @
d5d3c035
...
@@ -13,3 +13,9 @@ export const severityLevelVariant = {
...
@@ -13,3 +13,9 @@ export const severityLevelVariant = {
[
severityLevel
.
INFO
]:
'
info
'
,
[
severityLevel
.
INFO
]:
'
info
'
,
[
severityLevel
.
DEBUG
]:
'
light
'
,
[
severityLevel
.
DEBUG
]:
'
light
'
,
};
};
export
const
errorStatus
=
{
IGNORED
:
'
ignored
'
,
RESOLVED
:
'
resolved
'
,
UNRESOLVED
:
'
unresolved
'
,
};
app/assets/javascripts/error_tracking/components/error_details.vue
View file @
d5d3c035
...
@@ -11,7 +11,7 @@ import Stacktrace from './stacktrace.vue';
...
@@ -11,7 +11,7 @@ import Stacktrace from './stacktrace.vue';
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
trackClickErrorLinkToSentryOptions
}
from
'
../utils
'
;
import
{
trackClickErrorLinkToSentryOptions
}
from
'
../utils
'
;
import
{
severityLevel
,
severityLevelVariant
}
from
'
./constants
'
;
import
{
severityLevel
,
severityLevelVariant
,
errorStatus
}
from
'
./constants
'
;
import
query
from
'
../queries/details.query.graphql
'
;
import
query
from
'
../queries/details.query.graphql
'
;
...
@@ -32,10 +32,6 @@ export default {
...
@@ -32,10 +32,6 @@ export default {
},
},
mixins
:
[
timeagoMixin
],
mixins
:
[
timeagoMixin
],
props
:
{
props
:
{
listPath
:
{
type
:
String
,
required
:
true
,
},
issueUpdatePath
:
{
issueUpdatePath
:
{
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
...
@@ -80,6 +76,7 @@ export default {
...
@@ -80,6 +76,7 @@ export default {
result
(
res
)
{
result
(
res
)
{
if
(
res
.
data
.
project
?.
sentryDetailedError
)
{
if
(
res
.
data
.
project
?.
sentryDetailedError
)
{
this
.
$apollo
.
queries
.
GQLerror
.
stopPolling
();
this
.
$apollo
.
queries
.
GQLerror
.
stopPolling
();
this
.
setStatus
(
this
.
GQLerror
.
status
);
}
}
},
},
},
},
...
@@ -98,6 +95,7 @@ export default {
...
@@ -98,6 +95,7 @@ export default {
'
stacktraceData
'
,
'
stacktraceData
'
,
'
updatingResolveStatus
'
,
'
updatingResolveStatus
'
,
'
updatingIgnoreStatus
'
,
'
updatingIgnoreStatus
'
,
'
errorStatus
'
,
]),
]),
...
mapGetters
(
'
details
'
,
[
'
stacktrace
'
]),
...
mapGetters
(
'
details
'
,
[
'
stacktrace
'
]),
reported
()
{
reported
()
{
...
@@ -153,20 +151,40 @@ export default {
...
@@ -153,20 +151,40 @@ export default {
severityLevelVariant
[
this
.
error
.
tags
.
level
]
||
severityLevelVariant
[
severityLevel
.
ERROR
]
severityLevelVariant
[
this
.
error
.
tags
.
level
]
||
severityLevelVariant
[
severityLevel
.
ERROR
]
);
);
},
},
ignoreBtnLabel
()
{
return
this
.
errorStatus
!==
errorStatus
.
IGNORED
?
__
(
'
Ignore
'
)
:
__
(
'
Undo ignore
'
);
},
resolveBtnLabel
()
{
return
this
.
errorStatus
!==
errorStatus
.
RESOLVED
?
__
(
'
Resolve
'
)
:
__
(
'
Unresolve
'
);
},
},
},
mounted
()
{
mounted
()
{
this
.
startPollingDetails
(
this
.
issueDetailsPath
);
this
.
startPollingDetails
(
this
.
issueDetailsPath
);
this
.
startPollingStacktrace
(
this
.
issueStackTracePath
);
this
.
startPollingStacktrace
(
this
.
issueStackTracePath
);
},
},
methods
:
{
methods
:
{
...
mapActions
(
'
details
'
,
[
'
startPollingDetails
'
,
'
startPollingStacktrace
'
,
'
updateStatus
'
]),
...
mapActions
(
'
details
'
,
[
'
startPollingDetails
'
,
'
startPollingStacktrace
'
,
'
updateStatus
'
,
'
setStatus
'
,
'
updateResolveStatus
'
,
'
updateIgnoreStatus
'
,
]),
trackClickErrorLinkToSentryOptions
,
trackClickErrorLinkToSentryOptions
,
createIssue
()
{
createIssue
()
{
this
.
issueCreationInProgress
=
true
;
this
.
issueCreationInProgress
=
true
;
this
.
$refs
.
sentryIssueForm
.
submit
();
this
.
$refs
.
sentryIssueForm
.
submit
();
},
},
updateIssueStatus
(
status
)
{
onIgnoreStatusUpdate
()
{
this
.
updateStatus
({
endpoint
:
this
.
issueUpdatePath
,
redirectUrl
:
this
.
listPath
,
status
});
const
status
=
this
.
errorStatus
===
errorStatus
.
IGNORED
?
errorStatus
.
UNRESOLVED
:
errorStatus
.
IGNORED
;
this
.
updateIgnoreStatus
({
endpoint
:
this
.
issueUpdatePath
,
status
});
},
onResolveStatusUpdate
()
{
const
status
=
this
.
errorStatus
===
errorStatus
.
RESOLVED
?
errorStatus
.
UNRESOLVED
:
errorStatus
.
RESOLVED
;
this
.
updateResolveStatus
({
endpoint
:
this
.
issueUpdatePath
,
status
});
},
},
formatDate
(
date
)
{
formatDate
(
date
)
{
return
`
${
this
.
timeFormatted
(
date
)}
(
${
dateFormat
(
date
,
'
UTC:yyyy-mm-dd h:MM:ssTT Z
'
)}
)`
;
return
`
${
this
.
timeFormatted
(
date
)}
(
${
dateFormat
(
date
,
'
UTC:yyyy-mm-dd h:MM:ssTT Z
'
)}
)`
;
...
@@ -185,15 +203,17 @@ export default {
...
@@ -185,15 +203,17 @@ export default {
<span
v-if=
"!loadingStacktrace && stacktrace"
v-html=
"reported"
></span>
<span
v-if=
"!loadingStacktrace && stacktrace"
v-html=
"reported"
></span>
<div
class=
"d-inline-flex"
>
<div
class=
"d-inline-flex"
>
<loading-button
<loading-button
:label=
"
__('Ignore')
"
:label=
"
ignoreBtnLabel
"
:loading=
"updatingIgnoreStatus"
:loading=
"updatingIgnoreStatus"
@
click=
"updateIssueStatus('ignored')"
data-qa-selector=
"update_ignore_status_button"
@
click=
"onIgnoreStatusUpdate"
/>
/>
<loading-button
<loading-button
class=
"btn-outline-info ml-2"
class=
"btn-outline-info ml-2"
:label=
"
__('Resolve')
"
:label=
"
resolveBtnLabel
"
:loading=
"updatingResolveStatus"
:loading=
"updatingResolveStatus"
@
click=
"updateIssueStatus('resolved')"
data-qa-selector=
"update_resolve_status_button"
@
click=
"onResolveStatusUpdate"
/>
/>
<gl-button
<gl-button
v-if=
"error.gitlab_issue"
v-if=
"error.gitlab_issue"
...
...
app/assets/javascripts/error_tracking/details.js
View file @
d5d3c035
...
@@ -25,7 +25,6 @@ export default () => {
...
@@ -25,7 +25,6 @@ export default () => {
const
{
const
{
issueId
,
issueId
,
projectPath
,
projectPath
,
listPath
,
issueUpdatePath
,
issueUpdatePath
,
issueDetailsPath
,
issueDetailsPath
,
issueStackTracePath
,
issueStackTracePath
,
...
@@ -36,7 +35,6 @@ export default () => {
...
@@ -36,7 +35,6 @@ export default () => {
props
:
{
props
:
{
issueId
,
issueId
,
projectPath
,
projectPath
,
listPath
,
issueUpdatePath
,
issueUpdatePath
,
issueDetailsPath
,
issueDetailsPath
,
issueStackTracePath
,
issueStackTracePath
,
...
...
app/assets/javascripts/error_tracking/queries/details.query.graphql
View file @
d5d3c035
...
@@ -6,6 +6,7 @@ query errorDetails($fullPath: ID!, $errorId: ID!) {
...
@@ -6,6 +6,7 @@ query errorDetails($fullPath: ID!, $errorId: ID!) {
title
title
userCount
userCount
count
count
status
firstSeen
firstSeen
lastSeen
lastSeen
message
message
...
...
app/assets/javascripts/error_tracking/store/actions.js
View file @
d5d3c035
...
@@ -4,16 +4,33 @@ import createFlash from '~/flash';
...
@@ -4,16 +4,33 @@ import createFlash from '~/flash';
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
__
}
from
'
~/locale
'
;
export
function
updateStatus
({
commit
},
{
endpoint
,
redirectUrl
,
status
})
{
export
const
setStatus
=
({
commit
},
status
)
=>
{
const
type
=
commit
(
types
.
SET_ERROR_STATUS
,
status
.
toLowerCase
());
status
===
'
resolved
'
?
types
.
SET_UPDATING_RESOLVE_STATUS
:
types
.
SET_UPDATING_IGNORE_STATUS
;
};
commit
(
type
,
true
);
return
service
export
const
updateStatus
=
({
commit
},
{
endpoint
,
redirectUrl
,
status
})
=>
service
.
updateErrorStatus
(
endpoint
,
status
)
.
updateErrorStatus
(
endpoint
,
status
)
.
then
(()
=>
visitUrl
(
redirectUrl
))
.
then
(()
=>
{
.
catch
(()
=>
createFlash
(
__
(
'
Failed to update issue status
'
)))
if
(
redirectUrl
)
visitUrl
(
redirectUrl
);
.
finally
(()
=>
commit
(
type
,
false
));
commit
(
types
.
SET_ERROR_STATUS
,
status
);
}
})
.
catch
(()
=>
createFlash
(
__
(
'
Failed to update issue status
'
)));
export
const
updateResolveStatus
=
({
commit
,
dispatch
},
params
)
=>
{
commit
(
types
.
SET_UPDATING_RESOLVE_STATUS
,
true
);
return
dispatch
(
'
updateStatus
'
,
params
).
finally
(()
=>
{
commit
(
types
.
SET_UPDATING_RESOLVE_STATUS
,
false
);
});
};
export
const
updateIgnoreStatus
=
({
commit
,
dispatch
},
params
)
=>
{
commit
(
types
.
SET_UPDATING_IGNORE_STATUS
,
true
);
return
dispatch
(
'
updateStatus
'
,
params
).
finally
(()
=>
{
commit
(
types
.
SET_UPDATING_IGNORE_STATUS
,
false
);
});
};
export
default
()
=>
{};
export
default
()
=>
{};
app/assets/javascripts/error_tracking/store/details/state.js
View file @
d5d3c035
...
@@ -5,4 +5,5 @@ export default () => ({
...
@@ -5,4 +5,5 @@ export default () => ({
loadingStacktrace
:
true
,
loadingStacktrace
:
true
,
updatingResolveStatus
:
false
,
updatingResolveStatus
:
false
,
updatingIgnoreStatus
:
false
,
updatingIgnoreStatus
:
false
,
errorStatus
:
''
,
});
});
app/assets/javascripts/error_tracking/store/mutation_types.js
View file @
d5d3c035
export
const
SET_UPDATING_RESOLVE_STATUS
=
'
SET_UPDATING_RESOLVE_STATUS
'
;
export
const
SET_UPDATING_RESOLVE_STATUS
=
'
SET_UPDATING_RESOLVE_STATUS
'
;
export
const
SET_UPDATING_IGNORE_STATUS
=
'
SET_UPDATING_IGNORE_STATUS
'
;
export
const
SET_UPDATING_IGNORE_STATUS
=
'
SET_UPDATING_IGNORE_STATUS
'
;
export
const
SET_ERROR_STATUS
=
'
SET_ERROR_STATUS
'
;
app/assets/javascripts/error_tracking/store/mutations.js
View file @
d5d3c035
...
@@ -7,4 +7,7 @@ export default {
...
@@ -7,4 +7,7 @@ export default {
[
types
.
SET_UPDATING_RESOLVE_STATUS
](
state
,
updating
)
{
[
types
.
SET_UPDATING_RESOLVE_STATUS
](
state
,
updating
)
{
state
.
updatingResolveStatus
=
updating
;
state
.
updatingResolveStatus
=
updating
;
},
},
[
types
.
SET_ERROR_STATUS
](
state
,
status
)
{
state
.
errorStatus
=
status
;
},
};
};
app/graphql/resolvers/error_tracking/sentry_error_stack_trace_resolver.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
Resolvers
module
ErrorTracking
class
SentryErrorStackTraceResolver
<
BaseResolver
argument
:id
,
GraphQL
::
ID_TYPE
,
required:
true
,
description:
'ID of the Sentry issue'
def
resolve
(
**
args
)
issue_id
=
GlobalID
.
parse
(
args
[
:id
]).
model_id
# Get data from Sentry
response
=
::
ErrorTracking
::
IssueLatestEventService
.
new
(
project
,
current_user
,
{
issue_id:
issue_id
}
).
execute
event
=
response
[
:latest_event
]
event
.
gitlab_project
=
project
if
event
event
end
private
def
project
return
object
.
gitlab_project
if
object
.
respond_to?
(
:gitlab_project
)
object
end
end
end
end
app/graphql/types/error_tracking/sentry_error_collection_type.rb
View file @
d5d3c035
...
@@ -28,6 +28,10 @@ module Types
...
@@ -28,6 +28,10 @@ module Types
null:
true
,
null:
true
,
description:
'Detailed version of a Sentry error on the project'
,
description:
'Detailed version of a Sentry error on the project'
,
resolver:
Resolvers
::
ErrorTracking
::
SentryDetailedErrorResolver
resolver:
Resolvers
::
ErrorTracking
::
SentryDetailedErrorResolver
field
:error_stack_trace
,
Types
::
ErrorTracking
::
SentryErrorStackTraceType
,
null:
true
,
description:
'Stack Trace of Sentry Error'
,
resolver:
Resolvers
::
ErrorTracking
::
SentryErrorStackTraceResolver
field
:external_url
,
field
:external_url
,
GraphQL
::
STRING_TYPE
,
GraphQL
::
STRING_TYPE
,
null:
true
,
null:
true
,
...
...
app/graphql/types/error_tracking/sentry_error_stack_trace_context_type.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
Types
module
ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class
SentryErrorStackTraceContextType
<
::
Types
::
BaseObject
graphql_name
'SentryErrorStackTraceContext'
description
'An object context for a Sentry error stack trace'
field
:line
,
GraphQL
::
INT_TYPE
,
null:
false
,
description:
'Line number of the context'
field
:code
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Code number of the context'
def
line
object
[
0
]
end
def
code
object
[
1
]
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
app/graphql/types/error_tracking/sentry_error_stack_trace_entry_type.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
Types
module
ErrorTracking
# rubocop: disable Graphql/AuthorizeTypes
class
SentryErrorStackTraceEntryType
<
::
Types
::
BaseObject
graphql_name
'SentryErrorStackTraceEntry'
description
'An object containing a stack trace entry for a Sentry error.'
field
:function
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'Function in which the Sentry error occurred'
field
:col
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'Function in which the Sentry error occurred'
field
:line
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'Function in which the Sentry error occurred'
field
:file_name
,
GraphQL
::
STRING_TYPE
,
null:
true
,
description:
'File in which the Sentry error occurred'
field
:trace_context
,
[
Types
::
ErrorTracking
::
SentryErrorStackTraceContextType
],
null:
true
,
description:
'Context of the Sentry error'
def
function
object
[
'function'
]
end
def
col
object
[
'colNo'
]
end
def
line
object
[
'lineNo'
]
end
def
file_name
object
[
'filename'
]
end
def
trace_context
object
[
'context'
]
end
end
# rubocop: enable Graphql/AuthorizeTypes
end
end
app/graphql/types/error_tracking/sentry_error_stack_trace_type.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
Types
module
ErrorTracking
class
SentryErrorStackTraceType
<
::
Types
::
BaseObject
graphql_name
'SentryErrorStackTrace'
description
'An object containing a stack trace entry for a Sentry error.'
authorize
:read_sentry_issue
field
:issue_id
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'ID of the Sentry error'
field
:date_received
,
GraphQL
::
STRING_TYPE
,
null:
false
,
description:
'Time the stack trace was received by Sentry'
field
:stack_trace_entries
,
[
Types
::
ErrorTracking
::
SentryErrorStackTraceEntryType
],
null:
false
,
description:
'Stack trace entries for the Sentry error'
end
end
end
app/helpers/projects/error_tracking_helper.rb
View file @
d5d3c035
...
@@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper
...
@@ -22,7 +22,6 @@ module Projects::ErrorTrackingHelper
{
{
'issue-id'
=>
issue_id
,
'issue-id'
=>
issue_id
,
'project-path'
=>
project
.
full_path
,
'project-path'
=>
project
.
full_path
,
'list-path'
=>
project_error_tracking_index_path
(
project
),
'issue-details-path'
=>
details_project_error_tracking_index_path
(
*
opts
),
'issue-details-path'
=>
details_project_error_tracking_index_path
(
*
opts
),
'issue-update-path'
=>
update_project_error_tracking_index_path
(
*
opts
),
'issue-update-path'
=>
update_project_error_tracking_index_path
(
*
opts
),
'project-issues-path'
=>
project_issues_path
(
project
),
'project-issues-path'
=>
project_issues_path
(
project
),
...
...
app/views/explore/projects/_nav.html.haml
View file @
d5d3c035
.top-area
.top-area
%ul
.nav-links.nav.nav-tabs
%ul
.nav-links.nav.nav-tabs
=
nav_link
(
page:
[
trending_
explore_projects_path
,
explore_root_path
])
do
=
nav_link
(
page:
[
explore_projects_path
,
explore_root_path
])
do
=
link_to
trending_
explore_projects_path
do
=
link_to
explore_projects_path
do
=
_
(
'
Trending
'
)
=
_
(
'
All
'
)
=
nav_link
(
page:
starred_explore_projects_path
)
do
=
nav_link
(
page:
starred_explore_projects_path
)
do
=
link_to
starred_explore_projects_path
do
=
link_to
starred_explore_projects_path
do
=
_
(
'Most stars'
)
=
_
(
'Most stars'
)
=
nav_link
(
page:
explore_projects_path
)
do
=
nav_link
(
page:
trending_
explore_projects_path
)
do
=
link_to
explore_projects_path
do
=
link_to
trending_
explore_projects_path
do
=
_
(
'
All
'
)
=
_
(
'
Trending
'
)
.nav-controls
.nav-controls
-
unless
current_user
-
unless
current_user
...
...
changelogs/unreleased/19165-explore-projects-default-to-all.yml
0 → 100644
View file @
d5d3c035
---
title
:
Make Explore Projects default to All
merge_request
:
23811
author
:
type
:
changed
changelogs/unreleased/196881-reverse-actions-for-status-update.yml
0 → 100644
View file @
d5d3c035
---
title
:
Reverse actions for resolve/ignore Sentry issue
merge_request
:
23516
author
:
type
:
added
changelogs/unreleased/35896-graphql-error-stack-trace.yml
0 → 100644
View file @
d5d3c035
---
title
:
Add Sentry error stack trace to GraphQL API
merge_request
:
23750
author
:
type
:
added
changelogs/unreleased/refactoring-entities-file-8.yml
0 → 100644
View file @
d5d3c035
---
title
:
Separate snippet entities into own class files
merge_request
:
24183
author
:
Rajendra Kadam
type
:
added
config/mail_room.yml
View file @
d5d3c035
:mailboxes:
:mailboxes:
<%
<%
require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
config = Gitlab::MailRoom.config
Gitlab::MailRoom.enabled_configs.each do |config|
if Gitlab::MailRoom.enabled?
%>
%>
-
-
:host: <%= config[:host].to_json %>
:host: <%= config[:host].to_json %>
...
@@ -24,8 +22,8 @@
...
@@ -24,8 +22,8 @@
:delivery_options:
:delivery_options:
:redis_url: <%= config[:redis_url].to_json %>
:redis_url: <%= config[:redis_url].to_json %>
:namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %>
:namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %>
:queue:
email_receiver
:queue:
<%= config[:queue] %>
:worker:
EmailReceiverWorker
:worker:
<%= config[:worker] %>
<% if config[:sentinels] %>
<% if config[:sentinels] %>
:sentinels:
:sentinels:
<% config[:sentinels].each do |sentinel| %>
<% config[:sentinels].each do |sentinel| %>
...
...
config/routes/explore.rb
View file @
d5d3c035
...
@@ -8,7 +8,7 @@ namespace :explore do
...
@@ -8,7 +8,7 @@ namespace :explore do
resources
:groups
,
only:
[
:index
]
resources
:groups
,
only:
[
:index
]
resources
:snippets
,
only:
[
:index
]
resources
:snippets
,
only:
[
:index
]
root
to:
'projects#
trending
'
root
to:
'projects#
index
'
end
end
# Compatibility with old routing
# Compatibility with old routing
...
...
config/sidekiq_queues.yml
View file @
d5d3c035
...
@@ -224,6 +224,8 @@
...
@@ -224,6 +224,8 @@
-
2
-
2
-
-
self_monitoring_project_delete
-
-
self_monitoring_project_delete
-
2
-
2
-
-
service_desk_email_receiver
-
1
-
-
system_hook_push
-
-
system_hook_push
-
1
-
1
-
-
todos_destroyer
-
-
todos_destroyer
...
...
doc/administration/high_availability/README.md
View file @
d5d3c035
...
@@ -200,7 +200,7 @@ with the added complexity of many more nodes to configure, manage, and monitor.
...
@@ -200,7 +200,7 @@ with the added complexity of many more nodes to configure, manage, and monitor.
![
Fully Distributed architecture diagram
](
img/fully-distributed.png
)
![
Fully Distributed architecture diagram
](
img/fully-distributed.png
)
## Reference Architecture
Example
s
## Reference Architecture
Recommendation
s
The Support and Quality teams build, performance test, and validate Reference
The Support and Quality teams build, performance test, and validate Reference
Architectures that support large numbers of users. The specifications below are
Architectures that support large numbers of users. The specifications below are
...
...
doc/api/graphql/reference/gitlab_schema.graphql
View file @
d5d3c035
...
@@ -6298,6 +6298,16 @@ type SentryErrorCollection {
...
@@ -6298,6 +6298,16 @@ type SentryErrorCollection {
id
:
ID
!
id
:
ID
!
):
SentryDetailedError
):
SentryDetailedError
"""
Stack
Trace
of
Sentry
Error
"""
errorStackTrace
(
"""
ID
of
the
Sentry
issue
"""
id
:
ID
!
):
SentryErrorStackTrace
"""
"""
Collection
of
Sentry
Errors
Collection
of
Sentry
Errors
"""
"""
...
@@ -6386,6 +6396,71 @@ type SentryErrorFrequency {
...
@@ -6386,6 +6396,71 @@ type SentryErrorFrequency {
time
:
Time
!
time
:
Time
!
}
}
"""
An object containing a stack trace entry for a Sentry error.
"""
type
SentryErrorStackTrace
{
"""
Time
the
stack
trace
was
received
by
Sentry
"""
dateReceived
:
String
!
"""
ID
of
the
Sentry
error
"""
issueId
:
String
!
"""
Stack
trace
entries
for
the
Sentry
error
"""
stackTraceEntries
:
[
SentryErrorStackTraceEntry
!]!
}
"""
An object context for a Sentry error stack trace
"""
type
SentryErrorStackTraceContext
{
"""
Code
number
of
the
context
"""
code
:
String
!
"""
Line
number
of
the
context
"""
line
:
Int
!
}
"""
An object containing a stack trace entry for a Sentry error.
"""
type
SentryErrorStackTraceEntry
{
"""
Function
in
which
the
Sentry
error
occurred
"""
col
:
String
"""
File
in
which
the
Sentry
error
occurred
"""
fileName
:
String
"""
Function
in
which
the
Sentry
error
occurred
"""
function
:
String
"""
Function
in
which
the
Sentry
error
occurred
"""
line
:
String
"""
Context
of
the
Sentry
error
"""
traceContext
:
[
SentryErrorStackTraceContext
!]
}
"""
"""
State of a Sentry error
State of a Sentry error
"""
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
d5d3c035
...
@@ -17454,6 +17454,33 @@
...
@@ -17454,6 +17454,33 @@
"isDeprecated"
:
false
,
"isDeprecated"
:
false
,
"deprecationReason"
:
null
"deprecationReason"
:
null
},
},
{
"name"
:
"errorStackTrace"
,
"description"
:
"Stack Trace of Sentry Error"
,
"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"
:
"SentryErrorStackTrace"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
{
"name"
:
"errors"
,
"name"
:
"errors"
,
"description"
:
"Collection of Sentry Errors"
,
"description"
:
"Collection of Sentry Errors"
,
...
@@ -17984,6 +18011,221 @@
...
@@ -17984,6 +18011,221 @@
"enumValues"
:
null
,
"enumValues"
:
null
,
"possibleTypes"
:
null
"possibleTypes"
:
null
},
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorStackTrace"
,
"description"
:
"An object containing a stack trace entry for a Sentry error."
,
"fields"
:
[
{
"name"
:
"dateReceived"
,
"description"
:
"Time the stack trace was received by Sentry"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"issueId"
,
"description"
:
"ID of the Sentry error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"stackTraceEntries"
,
"description"
:
"Stack trace entries for the Sentry error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorStackTraceEntry"
,
"ofType"
:
null
}
}
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorStackTraceEntry"
,
"description"
:
"An object containing a stack trace entry for a Sentry error."
,
"fields"
:
[
{
"name"
:
"col"
,
"description"
:
"Function in which the Sentry error occurred"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"fileName"
,
"description"
:
"File in which the Sentry error occurred"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"function"
,
"description"
:
"Function in which the Sentry error occurred"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"line"
,
"description"
:
"Function in which the Sentry error occurred"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"traceContext"
,
"description"
:
"Context of the Sentry error"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"LIST"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorStackTraceContext"
,
"ofType"
:
null
}
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
}
],
"inputFields"
:
null
,
"interfaces"
:
[
],
"enumValues"
:
null
,
"possibleTypes"
:
null
},
{
"kind"
:
"OBJECT"
,
"name"
:
"SentryErrorStackTraceContext"
,
"description"
:
"An object context for a Sentry error stack trace"
,
"fields"
:
[
{
"name"
:
"code"
,
"description"
:
"Code number of the context"
,
"args"
:
[
],
"type"
:
{
"kind"
:
"NON_NULL"
,
"name"
:
null
,
"ofType"
:
{
"kind"
:
"SCALAR"
,
"name"
:
"String"
,
"ofType"
:
null
}
},
"isDeprecated"
:
false
,
"deprecationReason"
:
null
},
{
"name"
:
"line"
,
"description"
:
"Line number of the context"
,
"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"
,
"kind"
:
"OBJECT"
,
"name"
:
"Metadata"
,
"name"
:
"Metadata"
,
...
...
doc/api/graphql/reference/index.md
View file @
d5d3c035
...
@@ -983,6 +983,7 @@ An object containing a collection of Sentry errors, and a detailed error.
...
@@ -983,6 +983,7 @@ An object containing a collection of Sentry errors, and a detailed error.
| Name | Type | Description |
| Name | Type | Description |
| --- | ---- | ---------- |
| --- | ---- | ---------- |
|
`detailedError`
| SentryDetailedError | Detailed version of a Sentry error on the project |
|
`detailedError`
| SentryDetailedError | Detailed version of a Sentry error on the project |
|
`errorStackTrace`
| SentryErrorStackTrace | Stack Trace of Sentry Error |
|
`errors`
| SentryErrorConnection | Collection of Sentry Errors |
|
`errors`
| SentryErrorConnection | Collection of Sentry Errors |
|
`externalUrl`
| String | External URL for Sentry |
|
`externalUrl`
| String | External URL for Sentry |
...
@@ -993,6 +994,37 @@ An object containing a collection of Sentry errors, and a detailed error.
...
@@ -993,6 +994,37 @@ An object containing a collection of Sentry errors, and a detailed error.
|
`count`
| Int! | Count of errors received since the previously recorded time |
|
`count`
| Int! | Count of errors received since the previously recorded time |
|
`time`
| Time! | Time the error frequency stats were recorded |
|
`time`
| Time! | Time the error frequency stats were recorded |
## SentryErrorStackTrace
An object containing a stack trace entry for a Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`dateReceived`
| String! | Time the stack trace was received by Sentry |
|
`issueId`
| String! | ID of the Sentry error |
|
`stackTraceEntries`
| SentryErrorStackTraceEntry! => Array | Stack trace entries for the Sentry error |
## SentryErrorStackTraceContext
An object context for a Sentry error stack trace
| Name | Type | Description |
| --- | ---- | ---------- |
|
`code`
| String! | Code number of the context |
|
`line`
| Int! | Line number of the context |
## SentryErrorStackTraceEntry
An object containing a stack trace entry for a Sentry error.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`col`
| String | Function in which the Sentry error occurred |
|
`fileName`
| String | File in which the Sentry error occurred |
|
`function`
| String | Function in which the Sentry error occurred |
|
`line`
| String | Function in which the Sentry error occurred |
|
`traceContext`
| SentryErrorStackTraceContext! => Array | Context of the Sentry error |
## SentryErrorTags
## SentryErrorTags
State of a Sentry error
State of a Sentry error
...
...
lib/api/entities.rb
View file @
d5d3c035
...
@@ -128,75 +128,6 @@ module API
...
@@ -128,75 +128,6 @@ module API
end
end
end
end
class
BasicRef
<
Grape
::
Entity
expose
:type
,
:name
end
class
Branch
<
Grape
::
Entity
expose
:name
expose
:commit
,
using:
Entities
::
Commit
do
|
repo_branch
,
options
|
options
[
:project
].
repository
.
commit
(
repo_branch
.
dereferenced_target
)
end
expose
:merged
do
|
repo_branch
,
options
|
if
options
[
:merged_branch_names
]
options
[
:merged_branch_names
].
include?
(
repo_branch
.
name
)
else
options
[
:project
].
repository
.
merged_to_root_ref?
(
repo_branch
)
end
end
expose
:protected
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
protected?
(
options
[
:project
],
repo_branch
.
name
)
end
expose
:developers_can_push
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
developers_can?
(
:push
,
repo_branch
.
name
,
protected_refs:
options
[
:project
].
protected_branches
)
end
expose
:developers_can_merge
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
developers_can?
(
:merge
,
repo_branch
.
name
,
protected_refs:
options
[
:project
].
protected_branches
)
end
expose
:can_push
do
|
repo_branch
,
options
|
Gitlab
::
UserAccess
.
new
(
options
[
:current_user
],
project:
options
[
:project
]).
can_push_to_branch?
(
repo_branch
.
name
)
end
expose
:default
do
|
repo_branch
,
options
|
options
[
:project
].
default_branch
==
repo_branch
.
name
end
end
class
TreeObject
<
Grape
::
Entity
expose
:id
,
:name
,
:type
,
:path
expose
:mode
do
|
obj
,
options
|
filemode
=
obj
.
mode
filemode
=
"0"
+
filemode
if
filemode
.
length
<
6
filemode
end
end
class
Snippet
<
Grape
::
Entity
expose
:id
,
:title
,
:file_name
,
:description
,
:visibility
expose
:author
,
using:
Entities
::
UserBasic
expose
:updated_at
,
:created_at
expose
:project_id
expose
:web_url
do
|
snippet
|
Gitlab
::
UrlBuilder
.
build
(
snippet
)
end
end
class
ProjectSnippet
<
Snippet
end
class
PersonalSnippet
<
Snippet
expose
:raw_url
do
|
snippet
|
Gitlab
::
UrlBuilder
.
build
(
snippet
,
raw:
true
)
end
end
class
IssuableEntity
<
Grape
::
Entity
class
IssuableEntity
<
Grape
::
Entity
expose
:id
,
:iid
expose
:id
,
:iid
expose
(
:project_id
)
{
|
entity
|
entity
&
.
project
.
try
(
:id
)
}
expose
(
:project_id
)
{
|
entity
|
entity
&
.
project
.
try
(
:id
)
}
...
...
lib/api/entities/basic_ref.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
API
module
Entities
class
BasicRef
<
Grape
::
Entity
expose
:type
,
:name
end
end
end
lib/api/entities/branch.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
API
module
Entities
class
Branch
<
Grape
::
Entity
expose
:name
expose
:commit
,
using:
Entities
::
Commit
do
|
repo_branch
,
options
|
options
[
:project
].
repository
.
commit
(
repo_branch
.
dereferenced_target
)
end
expose
:merged
do
|
repo_branch
,
options
|
if
options
[
:merged_branch_names
]
options
[
:merged_branch_names
].
include?
(
repo_branch
.
name
)
else
options
[
:project
].
repository
.
merged_to_root_ref?
(
repo_branch
)
end
end
expose
:protected
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
protected?
(
options
[
:project
],
repo_branch
.
name
)
end
expose
:developers_can_push
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
developers_can?
(
:push
,
repo_branch
.
name
,
protected_refs:
options
[
:project
].
protected_branches
)
end
expose
:developers_can_merge
do
|
repo_branch
,
options
|
::
ProtectedBranch
.
developers_can?
(
:merge
,
repo_branch
.
name
,
protected_refs:
options
[
:project
].
protected_branches
)
end
expose
:can_push
do
|
repo_branch
,
options
|
Gitlab
::
UserAccess
.
new
(
options
[
:current_user
],
project:
options
[
:project
]).
can_push_to_branch?
(
repo_branch
.
name
)
end
expose
:default
do
|
repo_branch
,
options
|
options
[
:project
].
default_branch
==
repo_branch
.
name
end
end
end
end
lib/api/entities/personal_snippet.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
API
module
Entities
class
PersonalSnippet
<
Snippet
expose
:raw_url
do
|
snippet
|
Gitlab
::
UrlBuilder
.
build
(
snippet
,
raw:
true
)
end
end
end
end
lib/api/entities/project_snippet.rb
0 → 100644
View file @
d5d3c035
# frozen_String_literal: true
module
API
module
Entities
class
ProjectSnippet
<
Entities
::
Snippet
end
end
end
lib/api/entities/snippet.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
API
module
Entities
class
Snippet
<
Grape
::
Entity
expose
:id
,
:title
,
:file_name
,
:description
,
:visibility
expose
:author
,
using:
Entities
::
UserBasic
expose
:updated_at
,
:created_at
expose
:project_id
expose
:web_url
do
|
snippet
|
Gitlab
::
UrlBuilder
.
build
(
snippet
)
end
end
end
end
lib/api/entities/tree_object.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
module
API
module
Entities
class
TreeObject
<
Grape
::
Entity
expose
:id
,
:name
,
:type
,
:path
expose
:mode
do
|
obj
,
options
|
filemode
=
obj
.
mode
filemode
=
"0"
+
filemode
if
filemode
.
length
<
6
filemode
end
end
end
end
lib/gitlab/error_tracking/error_event.rb
View file @
d5d3c035
...
@@ -5,7 +5,11 @@ module Gitlab
...
@@ -5,7 +5,11 @@ module Gitlab
class
ErrorEvent
class
ErrorEvent
include
ActiveModel
::
Model
include
ActiveModel
::
Model
attr_accessor
:issue_id
,
:date_received
,
:stack_trace_entries
attr_accessor
:issue_id
,
:date_received
,
:stack_trace_entries
,
:gitlab_project
def
self
.
declarative_policy_class
'ErrorTracking::BasePolicy'
end
end
end
end
end
end
end
lib/gitlab/mail_room.rb
View file @
d5d3c035
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
require
'yaml'
require
'yaml'
require
'json'
require
'json'
require
'pathname'
require_relative
'redis/queues'
unless
defined?
(
Gitlab
::
Redis
::
Queues
)
require_relative
'redis/queues'
unless
defined?
(
Gitlab
::
Redis
::
Queues
)
# This service is run independently of the main Rails process,
# This service is run independently of the main Rails process,
...
@@ -21,39 +22,60 @@ module Gitlab
...
@@ -21,39 +22,60 @@ module Gitlab
log_path:
RAILS_ROOT_DIR
.
join
(
'log'
,
'mail_room_json.log'
)
log_path:
RAILS_ROOT_DIR
.
join
(
'log'
,
'mail_room_json.log'
)
}.
freeze
}.
freeze
# Email specific configuration which is merged with configuration
# fetched from YML config file.
ADDRESS_SPECIFIC_CONFIG
=
{
incoming_email:
{
queue:
'email_receiver'
,
worker:
'EmailReceiverWorker'
},
service_desk_email:
{
queue:
'service_desk_email_receiver'
,
worker:
'ServiceDeskEmailReceiverWorker'
}
}.
freeze
class
<<
self
class
<<
self
def
enabled
?
def
enabled
_configs
config
[
:enabled
]
&&
config
[
:address
]
@enabled_configs
||=
configs
.
select
{
|
config
|
enabled?
(
config
)
}
end
end
def
config
private
@config
||=
fetch_config
end
def
reset_config!
def
enabled?
(
config
)
@config
=
nil
config
[
:enabled
]
&&
!
config
[
:address
].
to_s
.
empty?
end
end
private
def
configs
ADDRESS_SPECIFIC_CONFIG
.
keys
.
map
{
|
key
|
fetch_config
(
key
)
}
end
def
fetch_config
def
fetch_config
(
config_key
)
return
{}
unless
File
.
exist?
(
config_file
)
return
{}
unless
File
.
exist?
(
config_file
)
config
=
load_from_yaml
||
{}
config
=
merged_configs
(
config_key
)
config
=
DEFAULT_CONFIG
.
merge
(
config
)
do
|
_key
,
oldval
,
newval
|
config
.
merge!
(
redis_config
)
if
enabled?
(
config
)
config
[
:log_path
]
=
File
.
expand_path
(
config
[
:log_path
],
RAILS_ROOT_DIR
)
config
end
def
merged_configs
(
config_key
)
yml_config
=
load_yaml
.
fetch
(
config_key
,
{})
specific_config
=
ADDRESS_SPECIFIC_CONFIG
.
fetch
(
config_key
,
{})
DEFAULT_CONFIG
.
merge
(
specific_config
,
yml_config
)
do
|
_key
,
oldval
,
newval
|
newval
.
nil?
?
oldval
:
newval
newval
.
nil?
?
oldval
:
newval
end
end
end
if
config
[
:enabled
]
&&
config
[
:address
]
def
redis_config
gitlab_redis_queues
=
Gitlab
::
Redis
::
Queues
.
new
(
rails_env
)
gitlab_redis_queues
=
Gitlab
::
Redis
::
Queues
.
new
(
rails_env
)
config
[
:redis_url
]
=
gitlab_redis_queues
.
url
config
=
{
redis_url:
gitlab_redis_queues
.
url
}
if
gitlab_redis_queues
.
sentinels?
if
gitlab_redis_queues
.
sentinels?
config
[
:sentinels
]
=
gitlab_redis_queues
.
sentinels
config
[
:sentinels
]
=
gitlab_redis_queues
.
sentinels
end
end
end
config
[
:log_path
]
=
File
.
expand_path
(
config
[
:log_path
],
RAILS_ROOT_DIR
)
config
config
end
end
...
@@ -65,8 +87,8 @@ module Gitlab
...
@@ -65,8 +87,8 @@ module Gitlab
ENV
[
'MAIL_ROOM_GITLAB_CONFIG_FILE'
]
||
File
.
expand_path
(
'../../config/gitlab.yml'
,
__dir__
)
ENV
[
'MAIL_ROOM_GITLAB_CONFIG_FILE'
]
||
File
.
expand_path
(
'../../config/gitlab.yml'
,
__dir__
)
end
end
def
load_
from_
yaml
def
load_yaml
YAML
.
load_file
(
config_file
)[
rails_env
].
deep_symbolize_keys
[
:incoming_email
]
@yaml
||=
YAML
.
load_file
(
config_file
)[
rails_env
].
deep_symbolize_keys
end
end
end
end
end
end
...
...
locale/gitlab.pot
View file @
d5d3c035
...
@@ -20259,6 +20259,9 @@ msgstr ""
...
@@ -20259,6 +20259,9 @@ msgstr ""
msgid "Undo"
msgid "Undo"
msgstr ""
msgstr ""
msgid "Undo ignore"
msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
msgstr ""
...
@@ -20310,6 +20313,9 @@ msgstr ""
...
@@ -20310,6 +20313,9 @@ msgstr ""
msgid "Unmarks this %{noun} as Work In Progress."
msgid "Unmarks this %{noun} as Work In Progress."
msgstr ""
msgstr ""
msgid "Unresolve"
msgstr ""
msgid "Unresolve discussion"
msgid "Unresolve discussion"
msgstr ""
msgstr ""
...
...
qa/qa/tools/delete_subgroups.rb
View file @
d5d3c035
...
@@ -26,30 +26,19 @@ module QA
...
@@ -26,30 +26,19 @@ module QA
group_id
=
fetch_group_id
group_id
=
fetch_group_id
sub_groups_head_response
=
head
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
group_id
}
/subgroups"
,
per_page:
"100"
).
url
sub_groups_head_response
=
head
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
group_id
}
/subgroups"
,
per_page:
"100"
).
url
total_sub_groups
=
sub_groups_head_response
.
headers
[
:x_total
]
total_sub_group_pages
=
sub_groups_head_response
.
headers
[
:x_total_pages
]
total_sub_group_pages
=
sub_groups_head_response
.
headers
[
:x_total_pages
]
STDOUT
.
puts
"total_sub_groups:
#{
total_sub_groups
}
"
sub_group_ids
=
fetch_subgroup_ids
(
group_id
,
total_sub_group_pages
)
STDOUT
.
puts
"
total_sub_group_pages:
#{
total_sub_group_pages
}
"
STDOUT
.
puts
"
Number of Sub Groups not already marked for deletion:
#{
sub_group_ids
.
length
}
"
total_sub_group_pages
.
to_i
.
times
do
|
page_no
|
delete_subgroups
(
sub_group_ids
)
unless
sub_group_ids
.
empty?
# Fetch all subgroups for the top level group
sub_groups_response
=
get
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
group_id
}
/subgroups"
,
per_page:
"100"
).
url
sub_group_ids
=
JSON
.
parse
(
sub_groups_response
.
body
).
map
{
|
subgroup
|
subgroup
[
"id"
]
}
if
sub_group_ids
.
any?
STDOUT
.
puts
"
\n
==== Current Page:
#{
page_no
+
1
}
====
\n
"
delete_subgroups
(
sub_group_ids
)
end
end
STDOUT
.
puts
"
\n
Done"
STDOUT
.
puts
"
\n
Done"
end
end
private
private
def
delete_subgroups
(
sub_group_ids
)
def
delete_subgroups
(
sub_group_ids
)
STDOUT
.
puts
"Deleting
#{
sub_group_ids
.
length
}
subgroups..."
sub_group_ids
.
each
do
|
subgroup_id
|
sub_group_ids
.
each
do
|
subgroup_id
|
delete_response
=
delete
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
subgroup_id
}
"
).
url
delete_response
=
delete
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
subgroup_id
}
"
).
url
dot_or_f
=
delete_response
.
code
==
202
?
"
\e
[32m.
\e
[0m"
:
"
\e
[31mF
\e
[0m"
dot_or_f
=
delete_response
.
code
==
202
?
"
\e
[32m.
\e
[0m"
:
"
\e
[31mF
\e
[0m"
...
@@ -61,6 +50,17 @@ module QA
...
@@ -61,6 +50,17 @@ module QA
group_search_response
=
get
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups"
,
search:
ENV
[
'GROUP_NAME_OR_PATH'
]
||
'gitlab-qa-sandbox-group'
).
url
group_search_response
=
get
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups"
,
search:
ENV
[
'GROUP_NAME_OR_PATH'
]
||
'gitlab-qa-sandbox-group'
).
url
JSON
.
parse
(
group_search_response
.
body
).
first
[
"id"
]
JSON
.
parse
(
group_search_response
.
body
).
first
[
"id"
]
end
end
def
fetch_subgroup_ids
(
group_id
,
group_pages
)
sub_groups_ids
=
[]
group_pages
.
to_i
.
times
do
|
page_no
|
sub_groups_response
=
get
Runtime
::
API
::
Request
.
new
(
@api_client
,
"/groups/
#{
group_id
}
/subgroups"
,
page:
(
page_no
+
1
).
to_s
,
per_page:
"100"
).
url
sub_groups_ids
.
concat
(
JSON
.
parse
(
sub_groups_response
.
body
).
reject
{
|
subgroup
|
!
subgroup
[
"marked_for_deletion_on"
].
nil?
}.
map
{
|
subgroup
|
subgroup
[
"id"
]
})
end
sub_groups_ids
.
uniq
end
end
end
end
end
end
end
spec/config/mail_room_spec.rb
View file @
d5d3c035
...
@@ -39,39 +39,31 @@ describe 'mail_room.yml' do
...
@@ -39,39 +39,31 @@ describe 'mail_room.yml' do
end
end
end
end
context
'when
incoming email is
enabled'
do
context
'when
both incoming email and service desk email are
enabled'
do
let
(
:gitlab_config_path
)
{
'spec/fixtures/config/mail_room_enabled.yml'
}
let
(
:gitlab_config_path
)
{
'spec/fixtures/config/mail_room_enabled.yml'
}
let
(
:queues_config_path
)
{
'spec/fixtures/config/redis_queues_new_format_host.yml'
}
let
(
:queues_config_path
)
{
'spec/fixtures/config/redis_queues_new_format_host.yml'
}
let
(
:gitlab_redis_queues
)
{
Gitlab
::
Redis
::
Queues
.
new
(
Rails
.
env
)
}
let
(
:gitlab_redis_queues
)
{
Gitlab
::
Redis
::
Queues
.
new
(
Rails
.
env
)
}
it
'contains the intended configuration'
do
it
'contains the intended configuration'
do
expect
(
configuration
[
:mailboxes
].
length
).
to
eq
(
1
)
expected_mailbox
=
{
mailbox
=
configuration
[
:mailboxes
].
first
host:
'imap.gmail.com'
,
port:
993
,
expect
(
mailbox
[
:host
]).
to
eq
(
'imap.gmail.com'
)
ssl:
true
,
expect
(
mailbox
[
:port
]).
to
eq
(
993
)
start_tls:
false
,
expect
(
mailbox
[
:ssl
]).
to
eq
(
true
)
email:
'gitlab-incoming@gmail.com'
,
expect
(
mailbox
[
:start_tls
]).
to
eq
(
false
)
password:
'[REDACTED]'
,
expect
(
mailbox
[
:email
]).
to
eq
(
'gitlab-incoming@gmail.com'
)
name:
'inbox'
,
expect
(
mailbox
[
:password
]).
to
eq
(
'[REDACTED]'
)
idle_timeout:
60
expect
(
mailbox
[
:name
]).
to
eq
(
'inbox'
)
}
expect
(
mailbox
[
:idle_timeout
]).
to
eq
(
60
)
expected_options
=
{
redis_url:
gitlab_redis_queues
.
url
,
redis_url
=
gitlab_redis_queues
.
url
sentinels:
gitlab_redis_queues
.
sentinels
sentinels
=
gitlab_redis_queues
.
sentinels
}
expect
(
mailbox
[
:delivery_options
][
:redis_url
]).
to
be_present
expect
(
configuration
[
:mailboxes
].
length
).
to
eq
(
2
)
expect
(
mailbox
[
:delivery_options
][
:redis_url
]).
to
eq
(
redis_url
)
expect
(
configuration
[
:mailboxes
]).
to
all
(
include
(
expected_mailbox
))
expect
(
configuration
[
:mailboxes
].
map
{
|
m
|
m
[
:delivery_options
]
}).
to
all
(
include
(
expected_options
))
expect
(
mailbox
[
:delivery_options
][
:sentinels
]).
to
be_present
expect
(
configuration
[
:mailboxes
].
map
{
|
m
|
m
[
:arbitration_options
]
}).
to
all
(
include
(
expected_options
))
expect
(
mailbox
[
:delivery_options
][
:sentinels
]).
to
eq
(
sentinels
)
expect
(
mailbox
[
:arbitration_options
][
:redis_url
]).
to
be_present
expect
(
mailbox
[
:arbitration_options
][
:redis_url
]).
to
eq
(
redis_url
)
expect
(
mailbox
[
:arbitration_options
][
:sentinels
]).
to
be_present
expect
(
mailbox
[
:arbitration_options
][
:sentinels
]).
to
eq
(
sentinels
)
end
end
end
end
...
...
spec/features/dashboard/shortcuts_spec.rb
View file @
d5d3c035
...
@@ -51,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
...
@@ -51,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find
(
'body'
).
send_keys
([
:shift
,
'P'
])
find
(
'body'
).
send_keys
([
:shift
,
'P'
])
find
(
'.nothing-here-block'
)
find
(
'.nothing-here-block'
)
expect
(
page
).
to
have_content
(
'Explore public groups to find projects to contribute to.'
)
expect
(
page
).
to
have_content
(
"This user doesn't have any personal projects"
)
end
end
end
end
...
...
spec/fixtures/config/mail_room_disabled.yml
View file @
d5d3c035
...
@@ -9,3 +9,14 @@ test:
...
@@ -9,3 +9,14 @@ test:
ssl
:
true
ssl
:
true
start_tls
:
false
start_tls
:
false
mailbox
:
"
inbox"
mailbox
:
"
inbox"
service_desk_email
:
enabled
:
false
address
:
"
gitlab-incoming+%{key}@gmail.com"
user
:
"
gitlab-incoming@gmail.com"
password
:
"
[REDACTED]"
host
:
"
imap.gmail.com"
port
:
993
ssl
:
true
start_tls
:
false
mailbox
:
"
inbox"
spec/fixtures/config/mail_room_enabled.yml
View file @
d5d3c035
...
@@ -9,3 +9,14 @@ test:
...
@@ -9,3 +9,14 @@ test:
ssl
:
true
ssl
:
true
start_tls
:
false
start_tls
:
false
mailbox
:
"
inbox"
mailbox
:
"
inbox"
service_desk_email
:
enabled
:
true
address
:
"
gitlab-incoming+%{key}@gmail.com"
user
:
"
gitlab-incoming@gmail.com"
password
:
"
[REDACTED]"
host
:
"
imap.gmail.com"
port
:
993
ssl
:
true
start_tls
:
false
mailbox
:
"
inbox"
spec/frontend/error_tracking/components/error_details_spec.js
View file @
d5d3c035
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
Vuex
from
'
vuex
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
GlLoadingIcon
,
GlLink
,
GlBadge
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
{
GlLoadingIcon
,
GlLink
,
GlBadge
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
LoadingButton
from
'
~/vue_shared/components/loading_button.vue
'
;
import
LoadingButton
from
'
~/vue_shared/components/loading_button.vue
'
;
import
Stacktrace
from
'
~/error_tracking/components/stacktrace.vue
'
;
import
Stacktrace
from
'
~/error_tracking/components/stacktrace.vue
'
;
import
ErrorDetails
from
'
~/error_tracking/components/error_details.vue
'
;
import
ErrorDetails
from
'
~/error_tracking/components/error_details.vue
'
;
import
{
severityLevel
,
severityLevelVariant
}
from
'
~/error_tracking/components/constants
'
;
import
{
severityLevel
,
severityLevelVariant
,
errorStatus
,
}
from
'
~/error_tracking/components/constants
'
;
const
localVue
=
createLocalVue
();
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
localVue
.
use
(
Vuex
);
...
@@ -56,6 +61,8 @@ describe('ErrorDetails', () => {
...
@@ -56,6 +61,8 @@ describe('ErrorDetails', () => {
actions
=
{
actions
=
{
startPollingDetails
:
()
=>
{},
startPollingDetails
:
()
=>
{},
startPollingStacktrace
:
()
=>
{},
startPollingStacktrace
:
()
=>
{},
updateIgnoreStatus
:
jest
.
fn
(),
updateResolveStatus
:
jest
.
fn
(),
};
};
getters
=
{
getters
=
{
...
@@ -219,6 +226,96 @@ describe('ErrorDetails', () => {
...
@@ -219,6 +226,96 @@ describe('ErrorDetails', () => {
});
});
});
});
describe
(
'
Status update
'
,
()
=>
{
const
findUpdateIgnoreStatusButton
=
()
=>
wrapper
.
find
(
'
[data-qa-selector="update_ignore_status_button"]
'
);
const
findUpdateResolveStatusButton
=
()
=>
wrapper
.
find
(
'
[data-qa-selector="update_resolve_status_button"]
'
);
afterEach
(()
=>
{
actions
.
updateIgnoreStatus
.
mockClear
();
actions
.
updateResolveStatus
.
mockClear
();
});
describe
(
'
when error is unresolved
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
details
.
errorStatus
=
errorStatus
.
UNRESOLVED
;
mountComponent
();
});
it
(
'
displays Ignore and Resolve buttons
'
,
()
=>
{
expect
(
findUpdateIgnoreStatusButton
().
text
()).
toBe
(
__
(
'
Ignore
'
));
expect
(
findUpdateResolveStatusButton
().
text
()).
toBe
(
__
(
'
Resolve
'
));
});
it
(
'
marks error as ignored when ignore button is clicked
'
,
()
=>
{
findUpdateIgnoreStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateIgnoreStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
IGNORED
}),
);
});
it
(
'
marks error as resolved when resolve button is clicked
'
,
()
=>
{
findUpdateResolveStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateResolveStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
RESOLVED
}),
);
});
});
describe
(
'
when error is ignored
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
details
.
errorStatus
=
errorStatus
.
IGNORED
;
mountComponent
();
});
it
(
'
displays Undo Ignore and Resolve buttons
'
,
()
=>
{
expect
(
findUpdateIgnoreStatusButton
().
text
()).
toBe
(
__
(
'
Undo ignore
'
));
expect
(
findUpdateResolveStatusButton
().
text
()).
toBe
(
__
(
'
Resolve
'
));
});
it
(
'
marks error as unresolved when ignore button is clicked
'
,
()
=>
{
findUpdateIgnoreStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateIgnoreStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
UNRESOLVED
}),
);
});
it
(
'
marks error as resolved when resolve button is clicked
'
,
()
=>
{
findUpdateResolveStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateResolveStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
RESOLVED
}),
);
});
});
describe
(
'
when error is resolved
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
details
.
errorStatus
=
errorStatus
.
RESOLVED
;
mountComponent
();
});
it
(
'
displays Ignore and Unresolve buttons
'
,
()
=>
{
expect
(
findUpdateIgnoreStatusButton
().
text
()).
toBe
(
__
(
'
Ignore
'
));
expect
(
findUpdateResolveStatusButton
().
text
()).
toBe
(
__
(
'
Unresolve
'
));
});
it
(
'
marks error as ignored when ignore button is clicked
'
,
()
=>
{
findUpdateIgnoreStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateIgnoreStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
IGNORED
}),
);
});
it
(
'
marks error as unresolved when unresolve button is clicked
'
,
()
=>
{
findUpdateResolveStatusButton
().
trigger
(
'
click
'
);
expect
(
actions
.
updateResolveStatus
.
mock
.
calls
[
0
][
1
]).
toEqual
(
expect
.
objectContaining
({
status
:
errorStatus
.
UNRESOLVED
}),
);
});
});
});
describe
(
'
GitLab issue link
'
,
()
=>
{
describe
(
'
GitLab issue link
'
,
()
=>
{
const
gitlabIssue
=
'
https://gitlab.example.com/issues/1
'
;
const
gitlabIssue
=
'
https://gitlab.example.com/issues/1
'
;
const
findGitLabLink
=
()
=>
wrapper
.
find
(
`[href="
${
gitlabIssue
}
"]`
);
const
findGitLabLink
=
()
=>
wrapper
.
find
(
`[href="
${
gitlabIssue
}
"]`
);
...
...
spec/frontend/error_tracking/store/actions_spec.js
View file @
d5d3c035
...
@@ -10,6 +10,8 @@ jest.mock('~/flash.js');
...
@@ -10,6 +10,8 @@ jest.mock('~/flash.js');
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
let
mock
;
let
mock
;
const
commit
=
jest
.
fn
();
const
dispatch
=
jest
.
fn
().
mockResolvedValue
();
describe
(
'
Sentry common store actions
'
,
()
=>
{
describe
(
'
Sentry common store actions
'
,
()
=>
{
beforeEach
(()
=>
{
beforeEach
(()
=>
{
...
@@ -20,26 +22,22 @@ describe('Sentry common store actions', () => {
...
@@ -20,26 +22,22 @@ describe('Sentry common store actions', () => {
mock
.
restore
();
mock
.
restore
();
createFlash
.
mockClear
();
createFlash
.
mockClear
();
});
});
const
endpoint
=
'
123/stacktrace
'
;
const
redirectUrl
=
'
/list
'
;
const
status
=
'
resolved
'
;
const
params
=
{
endpoint
,
redirectUrl
,
status
};
describe
(
'
updateStatus
'
,
()
=>
{
describe
(
'
updateStatus
'
,
()
=>
{
const
endpoint
=
'
123/stacktrace
'
;
const
redirectUrl
=
'
/list
'
;
const
status
=
'
resolved
'
;
it
(
'
should handle successful status update
'
,
done
=>
{
it
(
'
should handle successful status update
'
,
done
=>
{
mock
.
onPut
().
reply
(
200
,
{});
mock
.
onPut
().
reply
(
200
,
{});
testAction
(
testAction
(
actions
.
updateStatus
,
actions
.
updateStatus
,
{
endpoint
,
redirectUrl
,
status
}
,
params
,
{},
{},
[
[
{
{
payload
:
true
,
payload
:
'
resolved
'
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
type
:
types
.
SET_ERROR_STATUS
,
},
{
payload
:
false
,
type
:
'
SET_UPDATING_RESOLVE_STATUS
'
,
},
},
],
],
[],
[],
...
@@ -52,27 +50,29 @@ describe('Sentry common store actions', () => {
...
@@ -52,27 +50,29 @@ describe('Sentry common store actions', () => {
it
(
'
should handle unsuccessful status update
'
,
done
=>
{
it
(
'
should handle unsuccessful status update
'
,
done
=>
{
mock
.
onPut
().
reply
(
400
,
{});
mock
.
onPut
().
reply
(
400
,
{});
testAction
(
testAction
(
actions
.
updateStatus
,
params
,
{},
[],
[],
()
=>
{
actions
.
updateStatus
,
expect
(
visitUrl
).
not
.
toHaveBeenCalled
();
{
endpoint
,
redirectUrl
,
status
},
expect
(
createFlash
).
toHaveBeenCalledTimes
(
1
);
{},
done
();
[
});
{
payload
:
true
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
},
{
payload
:
false
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
},
],
[],
()
=>
{
expect
(
visitUrl
).
not
.
toHaveBeenCalled
();
expect
(
createFlash
).
toHaveBeenCalledTimes
(
1
);
done
();
},
);
});
});
});
});
describe
(
'
updateResolveStatus
'
,
()
=>
{
it
(
'
handles status update
'
,
()
=>
actions
.
updateResolveStatus
({
commit
,
dispatch
},
params
).
then
(()
=>
{
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
SET_UPDATING_RESOLVE_STATUS
,
true
);
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
SET_UPDATING_RESOLVE_STATUS
,
false
);
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
updateStatus
'
,
params
);
}));
});
describe
(
'
updateIgnoreStatus
'
,
()
=>
{
it
(
'
handles status update
'
,
()
=>
actions
.
updateIgnoreStatus
({
commit
,
dispatch
},
params
).
then
(()
=>
{
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
SET_UPDATING_IGNORE_STATUS
,
true
);
expect
(
commit
).
toHaveBeenCalledWith
(
types
.
SET_UPDATING_IGNORE_STATUS
,
false
);
expect
(
dispatch
).
toHaveBeenCalledWith
(
'
updateStatus
'
,
params
);
}));
});
});
});
spec/graphql/types/error_tracking/sentry_error_collection_type_spec.rb
View file @
d5d3c035
...
@@ -12,6 +12,7 @@ describe GitlabSchema.types['SentryErrorCollection'] do
...
@@ -12,6 +12,7 @@ describe GitlabSchema.types['SentryErrorCollection'] do
errors
errors
detailed_error
detailed_error
external_url
external_url
error_stack_trace
]
]
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
...
...
spec/graphql/types/error_tracking/sentry_error_stack_trace_entry_type_spec.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
require
'spec_helper'
describe
GitlabSchema
.
types
[
'SentryErrorStackTraceEntry'
]
do
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'SentryErrorStackTraceEntry'
)
}
it
'exposes the expected fields'
do
expected_fields
=
%i[
function
col
line
file_name
trace_context
]
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
end
end
spec/graphql/types/error_tracking/sentry_error_stack_trace_type_spec.rb
0 → 100644
View file @
d5d3c035
# frozen_string_literal: true
require
'spec_helper'
describe
GitlabSchema
.
types
[
'SentryErrorStackTrace'
]
do
it
{
expect
(
described_class
.
graphql_name
).
to
eq
(
'SentryErrorStackTrace'
)
}
it
{
expect
(
described_class
).
to
require_graphql_authorizations
(
:read_sentry_issue
)
}
it
'exposes the expected fields'
do
expected_fields
=
%i[
issue_id
date_received
stack_trace_entries
]
is_expected
.
to
have_graphql_fields
(
*
expected_fields
)
end
end
spec/helpers/projects/error_tracking_helper_spec.rb
View file @
d5d3c035
...
@@ -83,7 +83,6 @@ describe Projects::ErrorTrackingHelper do
...
@@ -83,7 +83,6 @@ describe Projects::ErrorTrackingHelper do
describe
'#error_details_data'
do
describe
'#error_details_data'
do
let
(
:issue_id
)
{
1234
}
let
(
:issue_id
)
{
1234
}
let
(
:route_params
)
{
[
project
.
owner
,
project
,
issue_id
,
{
format: :json
}]
}
let
(
:route_params
)
{
[
project
.
owner
,
project
,
issue_id
,
{
format: :json
}]
}
let
(
:list_path
)
{
project_error_tracking_index_path
(
project
)
}
let
(
:details_path
)
{
details_namespace_project_error_tracking_index_path
(
*
route_params
)
}
let
(
:details_path
)
{
details_namespace_project_error_tracking_index_path
(
*
route_params
)
}
let
(
:project_path
)
{
project
.
full_path
}
let
(
:project_path
)
{
project
.
full_path
}
let
(
:stack_trace_path
)
{
stack_trace_namespace_project_error_tracking_index_path
(
*
route_params
)
}
let
(
:stack_trace_path
)
{
stack_trace_namespace_project_error_tracking_index_path
(
*
route_params
)
}
...
@@ -91,10 +90,6 @@ describe Projects::ErrorTrackingHelper do
...
@@ -91,10 +90,6 @@ describe Projects::ErrorTrackingHelper do
let
(
:result
)
{
helper
.
error_details_data
(
project
,
issue_id
)
}
let
(
:result
)
{
helper
.
error_details_data
(
project
,
issue_id
)
}
it
'returns the correct list path'
do
expect
(
result
[
'list-path'
]).
to
eq
list_path
end
it
'returns the correct issue id'
do
it
'returns the correct issue id'
do
expect
(
result
[
'issue-id'
]).
to
eq
issue_id
expect
(
result
[
'issue-id'
]).
to
eq
issue_id
end
end
...
...
spec/lib/gitlab/mail_room/mail_room_spec.rb
View file @
d5d3c035
...
@@ -4,9 +4,10 @@ require 'spec_helper'
...
@@ -4,9 +4,10 @@ require 'spec_helper'
describe
Gitlab
::
MailRoom
do
describe
Gitlab
::
MailRoom
do
let
(
:default_port
)
{
143
}
let
(
:default_port
)
{
143
}
let
(
:
default
_config
)
do
let
(
:
yml
_config
)
do
{
{
enabled:
false
,
enabled:
true
,
address:
'address@example.com'
,
port:
default_port
,
port:
default_port
,
ssl:
false
,
ssl:
false
,
start_tls:
false
,
start_tls:
false
,
...
@@ -16,71 +17,73 @@ describe Gitlab::MailRoom do
...
@@ -16,71 +17,73 @@ describe Gitlab::MailRoom do
}
}
end
end
shared_examples_for
'only truthy if both enabled and address are truthy'
do
|
target_proc
|
let
(
:custom_config
)
{
{}
}
context
'with both enabled and address as truthy values'
do
let
(
:incoming_email_config
)
{
yml_config
.
merge
(
custom_config
)
}
it
'is truthy'
do
let
(
:service_desk_email_config
)
{
yml_config
.
merge
(
custom_config
)
}
stub_config
(
enabled:
true
,
address:
'localhost'
)
expect
(
target_proc
.
call
).
to
be_truthy
let
(
:configs
)
do
end
{
end
incoming_email:
incoming_email_config
,
service_desk_email:
service_desk_email_config
context
'with address only as truthy'
do
}
it
'is falsey'
do
end
stub_config
(
enabled:
false
,
address:
'localhost'
)
expect
(
target_proc
.
call
).
to
be_falsey
end
end
context
'with enabled only as truthy'
do
before
do
it
'is falsey'
do
described_class
.
instance_variable_set
(
:@enabled_configs
,
nil
)
stub_config
(
enabled:
true
,
address:
nil
)
end
expect
(
target_proc
.
call
).
to
be_falsey
describe
'#enabled_configs'
do
end
before
do
allow
(
described_class
).
to
receive
(
:load_yaml
).
and_return
(
configs
)
end
end
context
'with neither address nor enabled as truthy'
do
context
'when both email and address is set'
do
it
'is falsey'
do
it
'returns email configs'
do
stub_config
(
enabled:
false
,
address:
nil
)
expect
(
described_class
.
enabled_configs
.
size
).
to
eq
(
2
)
expect
(
target_proc
.
call
).
to
be_falsey
end
end
end
end
end
before
do
described_class
.
reset_config!
allow
(
File
).
to
receive
(
:exist?
).
and_return
true
end
describe
'#config'
do
context
'when the yml file cannot be found'
do
context
'if the yml file cannot be found'
do
before
do
before
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
false
allow
(
described_class
).
to
receive
(
:config_file
).
and_return
(
'not_existing_file'
)
end
end
it
'returns an empty
hash
'
do
it
'returns an empty
list
'
do
expect
(
described_class
.
config
).
to
be_empty
expect
(
described_class
.
enabled_configs
).
to
be_empty
end
end
end
end
before
do
context
'when email is disabled'
do
allow
(
described_class
).
to
receive
(
:load_from_yaml
).
and_return
(
default_config
)
let
(
:custom_config
)
{
{
enabled:
false
}
}
it
'returns an empty list'
do
expect
(
described_class
.
enabled_configs
).
to
be_empty
end
end
end
it
'sets up config properly
'
do
context
'when email is enabled but address is not set
'
do
expected_result
=
default_config
let
(
:custom_config
)
{
{
enabled:
true
,
address:
''
}
}
expect
(
described_class
.
config
).
to
match
expected_result
it
'returns an empty list'
do
expect
(
described_class
.
enabled_configs
).
to
be_empty
end
end
end
context
'when a config value is missing from the yml file'
do
context
'when a config value is missing from the yml file'
do
let
(
:yml_config
)
{
{}
}
let
(
:custom_config
)
{
{
enabled:
true
,
address:
'address@example.com'
}
}
it
'overwrites missing values with the default'
do
it
'overwrites missing values with the default'
do
stub_config
(
port:
nil
)
expect
(
described_class
.
enabled_configs
.
first
[
:port
]).
to
eq
(
Gitlab
::
MailRoom
::
DEFAULT_CONFIG
[
:port
])
end
end
context
'when only incoming_email config is present'
do
let
(
:configs
)
{
{
incoming_email:
incoming_email_config
}
}
expect
(
described_class
.
config
[
:port
]).
to
eq
default_port
it
'returns only encoming_email'
do
expect
(
described_class
.
enabled_configs
.
size
).
to
eq
(
1
)
expect
(
described_class
.
enabled_configs
.
first
[
:worker
]).
to
eq
(
'EmailReceiverWorker'
)
end
end
end
end
...
@@ -91,50 +94,31 @@ describe Gitlab::MailRoom do
...
@@ -91,50 +94,31 @@ describe Gitlab::MailRoom do
allow
(
Gitlab
::
Redis
::
Queues
).
to
receive
(
:new
).
and_return
(
fake_redis_queues
)
allow
(
Gitlab
::
Redis
::
Queues
).
to
receive
(
:new
).
and_return
(
fake_redis_queues
)
end
end
target_proc
=
proc
{
described_class
.
config
[
:redis_url
]
}
it
'sets redis config'
do
config
=
described_class
.
enabled_configs
.
first
it_behaves_like
'only truthy if both enabled and address are truthy'
,
target_proc
expect
(
config
[
:redis_url
]).
to
eq
(
'localhost'
)
expect
(
config
[
:sentinels
]).
to
eq
(
'yes, them'
)
end
end
end
describe
'setting up the log path'
do
describe
'setting up the log path'
do
context
'if the log path is a relative path'
do
context
'if the log path is a relative path'
do
it
'expands the log path to an absolute value'
do
let
(
:custom_config
)
{
{
log_path:
'tiny_log.log'
}
}
stub_config
(
log_path:
'tiny_log.log'
)
new_path
=
Pathname
.
new
(
described_class
.
config
[
:log_path
])
it
'expands the log path to an absolute value'
do
new_path
=
Pathname
.
new
(
described_class
.
enabled_configs
.
first
[
:log_path
])
expect
(
new_path
.
absolute?
).
to
be_truthy
expect
(
new_path
.
absolute?
).
to
be_truthy
end
end
end
end
context
'if the log path is absolute path'
do
context
'if the log path is absolute path'
do
it
'leaves the path as-is'
do
let
(
:custom_config
)
{
{
log_path:
'/dev/null'
}
}
new_path
=
'/dev/null'
stub_config
(
log_path:
new_path
)
expect
(
described_class
.
config
[
:log_path
]).
to
eq
new_path
it
'leaves the path as-is'
do
expect
(
described_class
.
enabled_configs
.
first
[
:log_path
]).
to
eq
'/dev/null'
end
end
end
end
end
end
end
end
describe
'#enabled?'
do
target_proc
=
proc
{
described_class
.
enabled?
}
it_behaves_like
'only truthy if both enabled and address are truthy'
,
target_proc
end
describe
'#reset_config?'
do
it
'resets config'
do
described_class
.
instance_variable_set
(
:@config
,
{
some_stuff:
'hooray'
})
described_class
.
reset_config!
expect
(
described_class
.
instance_variable_get
(
:@config
)).
to
be_nil
end
end
def
stub_config
(
override_values
)
modified_config
=
default_config
.
merge
(
override_values
)
allow
(
described_class
).
to
receive
(
:load_from_yaml
).
and_return
(
modified_config
)
end
end
end
spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
View file @
d5d3c035
...
@@ -40,8 +40,8 @@ describe 'sentry errors requests' do
...
@@ -40,8 +40,8 @@ describe 'sentry errors requests' do
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
it
"is expected to return an empty error"
do
it
'is expected to return an empty error'
do
expect
(
error_data
).
to
eq
nil
expect
(
error_data
).
to
be_
nil
end
end
end
end
...
@@ -49,7 +49,7 @@ describe 'sentry errors requests' do
...
@@ -49,7 +49,7 @@ describe 'sentry errors requests' do
before
do
before
do
allow_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
allow_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
to
receive
(
:issue_details
)
.
and_return
(
{
issue:
sentry_detailed_error
}
)
.
and_return
(
issue:
sentry_detailed_error
)
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
...
@@ -72,8 +72,8 @@ describe 'sentry errors requests' do
...
@@ -72,8 +72,8 @@ describe 'sentry errors requests' do
context
'user does not have permission'
do
context
'user does not have permission'
do
let
(
:current_user
)
{
create
(
:user
)
}
let
(
:current_user
)
{
create
(
:user
)
}
it
"is expected to return an empty error"
do
it
'is expected to return an empty error'
do
expect
(
error_data
).
to
eq
nil
expect
(
error_data
).
to
be_
nil
end
end
end
end
end
end
...
@@ -82,13 +82,13 @@ describe 'sentry errors requests' do
...
@@ -82,13 +82,13 @@ describe 'sentry errors requests' do
before
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
to
receive
(
:issue_details
)
.
and_return
(
{
error:
'error message'
}
)
.
and_return
(
error:
'error message'
)
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
it
'is expected to handle the error and return nil'
do
it
'is expected to handle the error and return nil'
do
expect
(
error_data
).
to
eq
nil
expect
(
error_data
).
to
be_
nil
end
end
end
end
end
end
...
@@ -132,8 +132,8 @@ describe 'sentry errors requests' do
...
@@ -132,8 +132,8 @@ describe 'sentry errors requests' do
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
it
"is expected to return nil"
do
it
'is expected to return nil'
do
expect
(
error_data
).
to
eq
nil
expect
(
error_data
).
to
be_
nil
end
end
end
end
...
@@ -141,7 +141,7 @@ describe 'sentry errors requests' do
...
@@ -141,7 +141,7 @@ describe 'sentry errors requests' do
before
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:list_sentry_issues
)
.
to
receive
(
:list_sentry_issues
)
.
and_return
(
{
issues:
[
sentry_error
],
pagination:
pagination
}
)
.
and_return
(
issues:
[
sentry_error
],
pagination:
pagination
)
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
...
@@ -174,17 +174,82 @@ describe 'sentry errors requests' do
...
@@ -174,17 +174,82 @@ describe 'sentry errors requests' do
end
end
end
end
context
"sentry api itself errors out"
do
context
'sentry api itself errors out'
do
before
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:list_sentry_issues
)
.
to
receive
(
:list_sentry_issues
)
.
and_return
(
{
error:
'error message'
}
)
.
and_return
(
error:
'error message'
)
post_graphql
(
query
,
current_user:
current_user
)
post_graphql
(
query
,
current_user:
current_user
)
end
end
it
'is expected to handle the error and return nil'
do
it
'is expected to handle the error and return nil'
do
expect
(
error_data
).
to
eq
nil
expect
(
error_data
).
to
be_nil
end
end
end
describe
'getting a stack trace'
do
let_it_be
(
:sentry_stack_trace
)
{
build
(
:error_tracking_error_event
)
}
let
(
:sentry_gid
)
{
Gitlab
::
ErrorTracking
::
DetailedError
.
new
(
id:
1
).
to_global_id
.
to_s
}
let
(
:stack_trace_fields
)
do
all_graphql_fields_for
(
'SentryErrorStackTrace'
.
classify
)
end
let
(
:fields
)
do
query_graphql_field
(
'errorStackTrace'
,
{
id:
sentry_gid
},
stack_trace_fields
)
end
let
(
:stack_trace_data
)
{
graphql_data
.
dig
(
'project'
,
'sentryErrors'
,
'errorStackTrace'
)
}
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
(
stack_trace_data
).
to
be_nil
end
end
context
'reactive cache returns data'
do
before
do
allow_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_latest_event
)
.
and_return
(
latest_event:
sentry_stack_trace
)
post_graphql
(
query
,
current_user:
current_user
)
end
it_behaves_like
'setting stack trace error'
context
'user does not have permission'
do
let
(
:current_user
)
{
create
(
:user
)
}
it
'is expected to return an empty error'
do
expect
(
stack_trace_data
).
to
be_nil
end
end
end
context
'sentry api returns an error'
do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_latest_event
)
.
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
(
stack_trace_data
).
to
be_nil
end
end
end
end
end
end
...
...
spec/support/shared_examples/error_tracking_shared_examples.rb
View file @
d5d3c035
...
@@ -3,11 +3,34 @@
...
@@ -3,11 +3,34 @@
RSpec
.
shared_examples
'setting sentry error data'
do
RSpec
.
shared_examples
'setting sentry error data'
do
it
'sets the sentry error data correctly'
do
it
'sets the sentry error data correctly'
do
aggregate_failures
'testing the sentry error is correct'
do
aggregate_failures
'testing the sentry error is correct'
do
expect
(
error
[
'id'
]).
to
eql
sentry_error
.
to_global_id
.
to_s
expect
(
error
[
'id'
]).
to
eq
sentry_error
.
to_global_id
.
to_s
expect
(
error
[
'sentryId'
]).
to
eql
sentry_error
.
id
.
to_s
expect
(
error
[
'sentryId'
]).
to
eq
sentry_error
.
id
.
to_s
expect
(
error
[
'status'
]).
to
eql
sentry_error
.
status
.
upcase
expect
(
error
[
'status'
]).
to
eq
sentry_error
.
status
.
upcase
expect
(
error
[
'firstSeen'
]).
to
eql
sentry_error
.
first_seen
expect
(
error
[
'firstSeen'
]).
to
eq
sentry_error
.
first_seen
expect
(
error
[
'lastSeen'
]).
to
eql
sentry_error
.
last_seen
expect
(
error
[
'lastSeen'
]).
to
eq
sentry_error
.
last_seen
end
end
end
RSpec
.
shared_examples
'setting stack trace error'
do
it
'sets the stack trace data correctly'
do
aggregate_failures
'testing the stack trace is correct'
do
expect
(
stack_trace_data
[
'dateReceived'
]).
to
eq
(
sentry_stack_trace
.
date_received
)
expect
(
stack_trace_data
[
'issueId'
]).
to
eq
(
sentry_stack_trace
.
issue_id
)
expect
(
stack_trace_data
[
'stackTraceEntries'
]).
to
be_an_instance_of
(
Array
)
expect
(
stack_trace_data
[
'stackTraceEntries'
].
size
).
to
eq
(
sentry_stack_trace
.
stack_trace_entries
.
size
)
end
end
it
'sets the stack trace entry data correctly'
do
aggregate_failures
'testing the stack trace entry is correct'
do
stack_trace_entry
=
stack_trace_data
[
'stackTraceEntries'
].
first
model_entry
=
sentry_stack_trace
.
stack_trace_entries
.
first
expect
(
stack_trace_entry
[
'function'
]).
to
eq
model_entry
[
'function'
]
expect
(
stack_trace_entry
[
'col'
]).
to
eq
model_entry
[
'colNo'
]
expect
(
stack_trace_entry
[
'line'
]).
to
eq
model_entry
[
'lineNo'
].
to_s
expect
(
stack_trace_entry
[
'fileName'
]).
to
eq
model_entry
[
'filename'
]
end
end
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