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
Show 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
unicode_utils (~> 1.4)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.
5
)
crass (1.0.
6
)
creole (0.5.0)
css_parser (1.7.0)
addressable
...
...
@@ -526,7 +526,7 @@ GEM
mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.
7.0
)
i18n (1.
8.2
)
concurrent-ruby (~> 1.0)
i18n_data (0.8.0)
icalendar (2.4.1)
...
...
app/assets/javascripts/error_tracking/components/constants.js
View file @
d5d3c035
...
...
@@ -13,3 +13,9 @@ export const severityLevelVariant = {
[
severityLevel
.
INFO
]:
'
info
'
,
[
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';
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
trackClickErrorLinkToSentryOptions
}
from
'
../utils
'
;
import
{
severityLevel
,
severityLevelVariant
}
from
'
./constants
'
;
import
{
severityLevel
,
severityLevelVariant
,
errorStatus
}
from
'
./constants
'
;
import
query
from
'
../queries/details.query.graphql
'
;
...
...
@@ -32,10 +32,6 @@ export default {
},
mixins
:
[
timeagoMixin
],
props
:
{
listPath
:
{
type
:
String
,
required
:
true
,
},
issueUpdatePath
:
{
type
:
String
,
required
:
true
,
...
...
@@ -80,6 +76,7 @@ export default {
result
(
res
)
{
if
(
res
.
data
.
project
?.
sentryDetailedError
)
{
this
.
$apollo
.
queries
.
GQLerror
.
stopPolling
();
this
.
setStatus
(
this
.
GQLerror
.
status
);
}
},
},
...
...
@@ -98,6 +95,7 @@ export default {
'
stacktraceData
'
,
'
updatingResolveStatus
'
,
'
updatingIgnoreStatus
'
,
'
errorStatus
'
,
]),
...
mapGetters
(
'
details
'
,
[
'
stacktrace
'
]),
reported
()
{
...
...
@@ -153,20 +151,40 @@ export default {
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
()
{
this
.
startPollingDetails
(
this
.
issueDetailsPath
);
this
.
startPollingStacktrace
(
this
.
issueStackTracePath
);
},
methods
:
{
...
mapActions
(
'
details
'
,
[
'
startPollingDetails
'
,
'
startPollingStacktrace
'
,
'
updateStatus
'
]),
...
mapActions
(
'
details
'
,
[
'
startPollingDetails
'
,
'
startPollingStacktrace
'
,
'
updateStatus
'
,
'
setStatus
'
,
'
updateResolveStatus
'
,
'
updateIgnoreStatus
'
,
]),
trackClickErrorLinkToSentryOptions
,
createIssue
()
{
this
.
issueCreationInProgress
=
true
;
this
.
$refs
.
sentryIssueForm
.
submit
();
},
updateIssueStatus
(
status
)
{
this
.
updateStatus
({
endpoint
:
this
.
issueUpdatePath
,
redirectUrl
:
this
.
listPath
,
status
});
onIgnoreStatusUpdate
()
{
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
)
{
return
`
${
this
.
timeFormatted
(
date
)}
(
${
dateFormat
(
date
,
'
UTC:yyyy-mm-dd h:MM:ssTT Z
'
)}
)`
;
...
...
@@ -185,15 +203,17 @@ export default {
<span
v-if=
"!loadingStacktrace && stacktrace"
v-html=
"reported"
></span>
<div
class=
"d-inline-flex"
>
<loading-button
:label=
"
__('Ignore')
"
:label=
"
ignoreBtnLabel
"
:loading=
"updatingIgnoreStatus"
@
click=
"updateIssueStatus('ignored')"
data-qa-selector=
"update_ignore_status_button"
@
click=
"onIgnoreStatusUpdate"
/>
<loading-button
class=
"btn-outline-info ml-2"
:label=
"
__('Resolve')
"
:label=
"
resolveBtnLabel
"
:loading=
"updatingResolveStatus"
@
click=
"updateIssueStatus('resolved')"
data-qa-selector=
"update_resolve_status_button"
@
click=
"onResolveStatusUpdate"
/>
<gl-button
v-if=
"error.gitlab_issue"
...
...
app/assets/javascripts/error_tracking/details.js
View file @
d5d3c035
...
...
@@ -25,7 +25,6 @@ export default () => {
const
{
issueId
,
projectPath
,
listPath
,
issueUpdatePath
,
issueDetailsPath
,
issueStackTracePath
,
...
...
@@ -36,7 +35,6 @@ export default () => {
props
:
{
issueId
,
projectPath
,
listPath
,
issueUpdatePath
,
issueDetailsPath
,
issueStackTracePath
,
...
...
app/assets/javascripts/error_tracking/queries/details.query.graphql
View file @
d5d3c035
...
...
@@ -6,6 +6,7 @@ query errorDetails($fullPath: ID!, $errorId: ID!) {
title
userCount
count
status
firstSeen
lastSeen
message
...
...
app/assets/javascripts/error_tracking/store/actions.js
View file @
d5d3c035
...
...
@@ -4,16 +4,33 @@ import createFlash from '~/flash';
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
{
__
}
from
'
~/locale
'
;
export
function
updateStatus
({
commit
},
{
endpoint
,
redirectUrl
,
status
})
{
const
type
=
status
===
'
resolved
'
?
types
.
SET_UPDATING_RESOLVE_STATUS
:
types
.
SET_UPDATING_IGNORE_STATUS
;
commit
(
type
,
true
);
export
const
setStatus
=
({
commit
},
status
)
=>
{
commit
(
types
.
SET_ERROR_STATUS
,
status
.
toLowerCase
());
};
return
service
export
const
updateStatus
=
({
commit
},
{
endpoint
,
redirectUrl
,
status
})
=>
service
.
updateErrorStatus
(
endpoint
,
status
)
.
then
(()
=>
visitUrl
(
redirectUrl
))
.
catch
(()
=>
createFlash
(
__
(
'
Failed to update issue status
'
)))
.
finally
(()
=>
commit
(
type
,
false
));
}
.
then
(()
=>
{
if
(
redirectUrl
)
visitUrl
(
redirectUrl
);
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
()
=>
{};
app/assets/javascripts/error_tracking/store/details/state.js
View file @
d5d3c035
...
...
@@ -5,4 +5,5 @@ export default () => ({
loadingStacktrace
:
true
,
updatingResolveStatus
:
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_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 {
[
types
.
SET_UPDATING_RESOLVE_STATUS
](
state
,
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
null:
true
,
description:
'Detailed version of a Sentry error on the project'
,
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
,
GraphQL
::
STRING_TYPE
,
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
{
'issue-id'
=>
issue_id
,
'project-path'
=>
project
.
full_path
,
'list-path'
=>
project_error_tracking_index_path
(
project
),
'issue-details-path'
=>
details_project_error_tracking_index_path
(
*
opts
),
'issue-update-path'
=>
update_project_error_tracking_index_path
(
*
opts
),
'project-issues-path'
=>
project_issues_path
(
project
),
...
...
app/views/explore/projects/_nav.html.haml
View file @
d5d3c035
.top-area
%ul
.nav-links.nav.nav-tabs
=
nav_link
(
page:
[
trending_
explore_projects_path
,
explore_root_path
])
do
=
link_to
trending_
explore_projects_path
do
=
_
(
'
Trending
'
)
=
nav_link
(
page:
[
explore_projects_path
,
explore_root_path
])
do
=
link_to
explore_projects_path
do
=
_
(
'
All
'
)
=
nav_link
(
page:
starred_explore_projects_path
)
do
=
link_to
starred_explore_projects_path
do
=
_
(
'Most stars'
)
=
nav_link
(
page:
explore_projects_path
)
do
=
link_to
explore_projects_path
do
=
_
(
'
All
'
)
=
nav_link
(
page:
trending_
explore_projects_path
)
do
=
link_to
trending_
explore_projects_path
do
=
_
(
'
Trending
'
)
.nav-controls
-
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:
<%
require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom)
config = Gitlab::MailRoom.config
if Gitlab::MailRoom.enabled?
Gitlab::MailRoom.enabled_configs.each do |config|
%>
-
:host: <%= config[:host].to_json %>
...
...
@@ -24,8 +22,8 @@
:delivery_options:
:redis_url: <%= config[:redis_url].to_json %>
:namespace: <%= Gitlab::Redis::Queues::SIDEKIQ_NAMESPACE %>
:queue:
email_receiver
:worker:
EmailReceiverWorker
:queue:
<%= config[:queue] %>
:worker:
<%= config[:worker] %>
<% if config[:sentinels] %>
:sentinels:
<% config[:sentinels].each do |sentinel| %>
...
...
config/routes/explore.rb
View file @
d5d3c035
...
...
@@ -8,7 +8,7 @@ namespace :explore do
resources
:groups
,
only:
[
:index
]
resources
:snippets
,
only:
[
:index
]
root
to:
'projects#
trending
'
root
to:
'projects#
index
'
end
# Compatibility with old routing
...
...
config/sidekiq_queues.yml
View file @
d5d3c035
...
...
@@ -224,6 +224,8 @@
-
2
-
-
self_monitoring_project_delete
-
2
-
-
service_desk_email_receiver
-
1
-
-
system_hook_push
-
1
-
-
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.
![
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
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 {
id
:
ID
!
):
SentryDetailedError
"""
Stack
Trace
of
Sentry
Error
"""
errorStackTrace
(
"""
ID
of
the
Sentry
issue
"""
id
:
ID
!
):
SentryErrorStackTrace
"""
Collection
of
Sentry
Errors
"""
...
...
@@ -6386,6 +6396,71 @@ type SentryErrorFrequency {
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
"""
...
...
doc/api/graphql/reference/gitlab_schema.json
View file @
d5d3c035
...
...
@@ -17454,6 +17454,33 @@
"isDeprecated"
:
false
,
"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"
,
"description"
:
"Collection of Sentry Errors"
,
...
...
@@ -17984,6 +18011,221 @@
"enumValues"
:
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"
,
"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.
| Name | Type | Description |
| --- | ---- | ---------- |
|
`detailedError`
| SentryDetailedError | Detailed version of a Sentry error on the project |
|
`errorStackTrace`
| SentryErrorStackTrace | Stack Trace of Sentry Error |
|
`errors`
| SentryErrorConnection | Collection of Sentry Errors |
|
`externalUrl`
| String | External URL for Sentry |
...
...
@@ -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 |
|
`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
State of a Sentry error
...
...
lib/api/entities.rb
View file @
d5d3c035
...
...
@@ -128,75 +128,6 @@ module API
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
expose
:id
,
:iid
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
class
ErrorEvent
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
lib/gitlab/mail_room.rb
View file @
d5d3c035
...
...
@@ -2,6 +2,7 @@
require
'yaml'
require
'json'
require
'pathname'
require_relative
'redis/queues'
unless
defined?
(
Gitlab
::
Redis
::
Queues
)
# This service is run independently of the main Rails process,
...
...
@@ -21,39 +22,60 @@ module Gitlab
log_path:
RAILS_ROOT_DIR
.
join
(
'log'
,
'mail_room_json.log'
)
}.
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
def
enabled
?
config
[
:enabled
]
&&
config
[
:address
]
def
enabled
_configs
@enabled_configs
||=
configs
.
select
{
|
config
|
enabled?
(
config
)
}
end
def
config
@config
||=
fetch_config
end
private
def
reset_config!
@config
=
nil
def
enabled?
(
config
)
config
[
:enabled
]
&&
!
config
[
:address
].
to_s
.
empty?
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
)
config
=
load_from_yaml
||
{}
config
=
DEFAULT_CONFIG
.
merge
(
config
)
do
|
_key
,
oldval
,
newval
|
config
=
merged_configs
(
config_key
)
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
end
end
if
config
[
:enabled
]
&&
config
[
:address
]
def
redis_config
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?
config
[
:sentinels
]
=
gitlab_redis_queues
.
sentinels
end
end
config
[
:log_path
]
=
File
.
expand_path
(
config
[
:log_path
],
RAILS_ROOT_DIR
)
config
end
...
...
@@ -65,8 +87,8 @@ module Gitlab
ENV
[
'MAIL_ROOM_GITLAB_CONFIG_FILE'
]
||
File
.
expand_path
(
'../../config/gitlab.yml'
,
__dir__
)
end
def
load_
from_
yaml
YAML
.
load_file
(
config_file
)[
rails_env
].
deep_symbolize_keys
[
:incoming_email
]
def
load_yaml
@yaml
||=
YAML
.
load_file
(
config_file
)[
rails_env
].
deep_symbolize_keys
end
end
end
...
...
locale/gitlab.pot
View file @
d5d3c035
...
...
@@ -20259,6 +20259,9 @@ msgstr ""
msgid "Undo"
msgstr ""
msgid "Undo ignore"
msgstr ""
msgid "Unfortunately, your email message to GitLab could not be processed."
msgstr ""
...
...
@@ -20310,6 +20313,9 @@ msgstr ""
msgid "Unmarks this %{noun} as Work In Progress."
msgstr ""
msgid "Unresolve"
msgstr ""
msgid "Unresolve discussion"
msgstr ""
...
...
qa/qa/tools/delete_subgroups.rb
View file @
d5d3c035
...
...
@@ -26,30 +26,19 @@ module QA
group_id
=
fetch_group_id
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
]
STDOUT
.
puts
"total_sub_groups:
#{
total_sub_groups
}
"
STDOUT
.
puts
"
total_sub_group_pages:
#{
total_sub_group_pages
}
"
sub_group_ids
=
fetch_subgroup_ids
(
group_id
,
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
|
# 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
delete_subgroups
(
sub_group_ids
)
unless
sub_group_ids
.
empty?
STDOUT
.
puts
"
\n
Done"
end
private
def
delete_subgroups
(
sub_group_ids
)
STDOUT
.
puts
"Deleting
#{
sub_group_ids
.
length
}
subgroups..."
sub_group_ids
.
each
do
|
subgroup_id
|
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"
...
...
@@ -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
JSON
.
parse
(
group_search_response
.
body
).
first
[
"id"
]
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
spec/config/mail_room_spec.rb
View file @
d5d3c035
...
...
@@ -39,39 +39,31 @@ describe 'mail_room.yml' do
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
(
:queues_config_path
)
{
'spec/fixtures/config/redis_queues_new_format_host.yml'
}
let
(
:gitlab_redis_queues
)
{
Gitlab
::
Redis
::
Queues
.
new
(
Rails
.
env
)
}
it
'contains the intended configuration'
do
expect
(
configuration
[
:mailboxes
].
length
).
to
eq
(
1
)
mailbox
=
configuration
[
:mailboxes
].
first
expect
(
mailbox
[
:host
]).
to
eq
(
'imap.gmail.com'
)
expect
(
mailbox
[
:port
]).
to
eq
(
993
)
expect
(
mailbox
[
:ssl
]).
to
eq
(
true
)
expect
(
mailbox
[
:start_tls
]).
to
eq
(
false
)
expect
(
mailbox
[
:email
]).
to
eq
(
'gitlab-incoming@gmail.com'
)
expect
(
mailbox
[
:password
]).
to
eq
(
'[REDACTED]'
)
expect
(
mailbox
[
:name
]).
to
eq
(
'inbox'
)
expect
(
mailbox
[
:idle_timeout
]).
to
eq
(
60
)
redis_url
=
gitlab_redis_queues
.
url
sentinels
=
gitlab_redis_queues
.
sentinels
expect
(
mailbox
[
:delivery_options
][
:redis_url
]).
to
be_present
expect
(
mailbox
[
:delivery_options
][
:redis_url
]).
to
eq
(
redis_url
)
expect
(
mailbox
[
:delivery_options
][
:sentinels
]).
to
be_present
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
)
expected_mailbox
=
{
host:
'imap.gmail.com'
,
port:
993
,
ssl:
true
,
start_tls:
false
,
email:
'gitlab-incoming@gmail.com'
,
password:
'[REDACTED]'
,
name:
'inbox'
,
idle_timeout:
60
}
expected_options
=
{
redis_url:
gitlab_redis_queues
.
url
,
sentinels:
gitlab_redis_queues
.
sentinels
}
expect
(
mailbox
[
:arbitration_options
][
:sentinels
]).
to
be_present
expect
(
mailbox
[
:arbitration_options
][
:sentinels
]).
to
eq
(
sentinels
)
expect
(
configuration
[
:mailboxes
].
length
).
to
eq
(
2
)
expect
(
configuration
[
:mailboxes
]).
to
all
(
include
(
expected_mailbox
))
expect
(
configuration
[
:mailboxes
].
map
{
|
m
|
m
[
:delivery_options
]
}).
to
all
(
include
(
expected_options
))
expect
(
configuration
[
:mailboxes
].
map
{
|
m
|
m
[
:arbitration_options
]
}).
to
all
(
include
(
expected_options
))
end
end
...
...
spec/features/dashboard/shortcuts_spec.rb
View file @
d5d3c035
...
...
@@ -51,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find
(
'body'
).
send_keys
([
:shift
,
'P'
])
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
...
...
spec/fixtures/config/mail_room_disabled.yml
View file @
d5d3c035
...
...
@@ -9,3 +9,14 @@ test:
ssl
:
true
start_tls
:
false
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:
ssl
:
true
start_tls
:
false
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
Vuex
from
'
vuex
'
;
import
{
__
}
from
'
~/locale
'
;
import
{
GlLoadingIcon
,
GlLink
,
GlBadge
,
GlFormInput
}
from
'
@gitlab/ui
'
;
import
LoadingButton
from
'
~/vue_shared/components/loading_button.vue
'
;
import
Stacktrace
from
'
~/error_tracking/components/stacktrace.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
();
localVue
.
use
(
Vuex
);
...
...
@@ -56,6 +61,8 @@ describe('ErrorDetails', () => {
actions
=
{
startPollingDetails
:
()
=>
{},
startPollingStacktrace
:
()
=>
{},
updateIgnoreStatus
:
jest
.
fn
(),
updateResolveStatus
:
jest
.
fn
(),
};
getters
=
{
...
...
@@ -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
'
,
()
=>
{
const
gitlabIssue
=
'
https://gitlab.example.com/issues/1
'
;
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');
jest
.
mock
(
'
~/lib/utils/url_utility
'
);
let
mock
;
const
commit
=
jest
.
fn
();
const
dispatch
=
jest
.
fn
().
mockResolvedValue
();
describe
(
'
Sentry common store actions
'
,
()
=>
{
beforeEach
(()
=>
{
...
...
@@ -20,26 +22,22 @@ describe('Sentry common store actions', () => {
mock
.
restore
();
createFlash
.
mockClear
();
});
describe
(
'
updateStatus
'
,
()
=>
{
const
endpoint
=
'
123/stacktrace
'
;
const
redirectUrl
=
'
/list
'
;
const
status
=
'
resolved
'
;
const
params
=
{
endpoint
,
redirectUrl
,
status
};
describe
(
'
updateStatus
'
,
()
=>
{
it
(
'
should handle successful status update
'
,
done
=>
{
mock
.
onPut
().
reply
(
200
,
{});
testAction
(
actions
.
updateStatus
,
{
endpoint
,
redirectUrl
,
status
}
,
params
,
{},
[
{
payload
:
true
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
},
{
payload
:
false
,
type
:
'
SET_UPDATING_RESOLVE_STATUS
'
,
payload
:
'
resolved
'
,
type
:
types
.
SET_ERROR_STATUS
,
},
],
[],
...
...
@@ -52,27 +50,29 @@ describe('Sentry common store actions', () => {
it
(
'
should handle unsuccessful status update
'
,
done
=>
{
mock
.
onPut
().
reply
(
400
,
{});
testAction
(
actions
.
updateStatus
,
{
endpoint
,
redirectUrl
,
status
},
{},
[
{
payload
:
true
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
},
{
payload
:
false
,
type
:
types
.
SET_UPDATING_RESOLVE_STATUS
,
},
],
[],
()
=>
{
testAction
(
actions
.
updateStatus
,
params
,
{},
[],
[],
()
=>
{
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
errors
detailed_error
external_url
error_stack_trace
]
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
describe
'#error_details_data'
do
let
(
:issue_id
)
{
1234
}
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
(
:project_path
)
{
project
.
full_path
}
let
(
:stack_trace_path
)
{
stack_trace_namespace_project_error_tracking_index_path
(
*
route_params
)
}
...
...
@@ -91,10 +90,6 @@ describe Projects::ErrorTrackingHelper do
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
expect
(
result
[
'issue-id'
]).
to
eq
issue_id
end
...
...
spec/lib/gitlab/mail_room/mail_room_spec.rb
View file @
d5d3c035
...
...
@@ -4,9 +4,10 @@ require 'spec_helper'
describe
Gitlab
::
MailRoom
do
let
(
:default_port
)
{
143
}
let
(
:
default
_config
)
do
let
(
:
yml
_config
)
do
{
enabled:
false
,
enabled:
true
,
address:
'address@example.com'
,
port:
default_port
,
ssl:
false
,
start_tls:
false
,
...
...
@@ -16,71 +17,73 @@ describe Gitlab::MailRoom do
}
end
shared_examples_for
'only truthy if both enabled and address are truthy'
do
|
target_proc
|
context
'with both enabled and address as truthy values'
do
it
'is truthy'
do
stub_config
(
enabled:
true
,
address:
'localhost'
)
let
(
:custom_config
)
{
{}
}
let
(
:incoming_email_config
)
{
yml_config
.
merge
(
custom_config
)
}
let
(
:service_desk_email_config
)
{
yml_config
.
merge
(
custom_config
)
}
expect
(
target_proc
.
call
).
to
be_truthy
end
let
(
:configs
)
do
{
incoming_email:
incoming_email_config
,
service_desk_email:
service_desk_email_config
}
end
context
'with address only as truthy'
do
it
'is falsey'
do
stub_config
(
enabled:
false
,
address:
'localhost'
)
expect
(
target_proc
.
call
).
to
be_falsey
end
before
do
described_class
.
instance_variable_set
(
:@enabled_configs
,
nil
)
end
context
'with enabled only as truthy'
do
it
'is falsey'
do
stub_config
(
enabled:
true
,
address:
nil
)
expect
(
target_proc
.
call
).
to
be_falsey
end
describe
'#enabled_configs'
do
before
do
allow
(
described_class
).
to
receive
(
:load_yaml
).
and_return
(
configs
)
end
context
'with neither address nor enabled as truthy'
do
it
'is falsey'
do
stub_config
(
enabled:
false
,
address:
nil
)
expect
(
target_proc
.
call
).
to
be_falsey
end
context
'when both email and address is set'
do
it
'returns email configs'
do
expect
(
described_class
.
enabled_configs
.
size
).
to
eq
(
2
)
end
end
context
'when the yml file cannot be found'
do
before
do
described_class
.
reset_config!
allow
(
File
).
to
receive
(
:exist?
).
and_return
true
allow
(
described_class
).
to
receive
(
:config_file
).
and_return
(
'not_existing_file'
)
end
describe
'#config'
do
context
'if the yml file cannot be found'
do
before
do
allow
(
File
).
to
receive
(
:exist?
).
and_return
false
end
it
'returns an empty hash'
do
expect
(
described_class
.
config
).
to
be_empty
it
'returns an empty list'
do
expect
(
described_class
.
enabled_configs
).
to
be_empty
end
end
before
do
allow
(
described_class
).
to
receive
(
:load_from_yaml
).
and_return
(
default_config
)
context
'when email is disabled'
do
let
(
:custom_config
)
{
{
enabled:
false
}
}
it
'returns an empty list'
do
expect
(
described_class
.
enabled_configs
).
to
be_empty
end
end
it
'sets up config properly
'
do
expected_result
=
default_config
context
'when email is enabled but address is not set
'
do
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
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
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
...
...
@@ -91,50 +94,31 @@ describe Gitlab::MailRoom do
allow
(
Gitlab
::
Redis
::
Queues
).
to
receive
(
:new
).
and_return
(
fake_redis_queues
)
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
describe
'setting up the log path'
do
context
'if the log path is a relative path'
do
it
'expands the log path to an absolute value'
do
stub_config
(
log_path:
'tiny_log.log'
)
let
(
:custom_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
end
end
context
'if the log path is absolute path'
do
it
'leaves the path as-is'
do
new_path
=
'/dev/null'
stub_config
(
log_path:
new_path
)
expect
(
described_class
.
config
[
:log_path
]).
to
eq
new_path
end
end
end
end
let
(
:custom_config
)
{
{
log_path:
'/dev/null'
}
}
describe
'#enabled?'
do
target_proc
=
proc
{
described_class
.
enabled?
}
it_behaves_like
'only truthy if both enabled and address are truthy'
,
target_proc
it
'leaves the path as-is'
do
expect
(
described_class
.
enabled_configs
.
first
[
:log_path
]).
to
eq
'/dev/null'
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
spec/requests/api/graphql/project/error_tracking/sentry_errors_request_spec.rb
View file @
d5d3c035
...
...
@@ -40,8 +40,8 @@ describe 'sentry errors requests' do
post_graphql
(
query
,
current_user:
current_user
)
end
it
"is expected to return an empty error"
do
expect
(
error_data
).
to
eq
nil
it
'is expected to return an empty error'
do
expect
(
error_data
).
to
be_
nil
end
end
...
...
@@ -49,7 +49,7 @@ describe 'sentry errors requests' do
before
do
allow_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
and_return
(
{
issue:
sentry_detailed_error
}
)
.
and_return
(
issue:
sentry_detailed_error
)
post_graphql
(
query
,
current_user:
current_user
)
end
...
...
@@ -72,8 +72,8 @@ describe 'sentry errors requests' do
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
it
'is expected to return an empty error'
do
expect
(
error_data
).
to
be_
nil
end
end
end
...
...
@@ -82,13 +82,13 @@ describe 'sentry errors requests' do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
to
receive
(
:issue_details
)
.
and_return
(
{
error:
'error message'
}
)
.
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
expect
(
error_data
).
to
be_
nil
end
end
end
...
...
@@ -132,8 +132,8 @@ describe 'sentry errors requests' do
post_graphql
(
query
,
current_user:
current_user
)
end
it
"is expected to return nil"
do
expect
(
error_data
).
to
eq
nil
it
'is expected to return nil'
do
expect
(
error_data
).
to
be_
nil
end
end
...
...
@@ -141,7 +141,7 @@ describe 'sentry errors requests' do
before
do
expect_any_instance_of
(
ErrorTracking
::
ProjectErrorTrackingSetting
)
.
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
)
end
...
...
@@ -174,17 +174,82 @@ describe 'sentry errors requests' do
end
end
context
"sentry api itself errors out"
do
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'
}
)
.
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
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
...
...
spec/support/shared_examples/error_tracking_shared_examples.rb
View file @
d5d3c035
...
...
@@ -3,11 +3,34 @@
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
expect
(
error
[
'id'
]).
to
eq
sentry_error
.
to_global_id
.
to_s
expect
(
error
[
'sentryId'
]).
to
eq
sentry_error
.
id
.
to_s
expect
(
error
[
'status'
]).
to
eq
sentry_error
.
status
.
upcase
expect
(
error
[
'firstSeen'
]).
to
eq
sentry_error
.
first_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
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