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
791794a0
Commit
791794a0
authored
Nov 11, 2019
by
Olena Horal-Koretska
Committed by
Fatih Acet
Nov 11, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Detail view of Sentry error in GitLab
Error details page with stacktrace
parent
333d4236
Changes
33
Show whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
823 additions
and
30 deletions
+823
-30
app/assets/javascripts/error_tracking/components/error_details.vue
...s/javascripts/error_tracking/components/error_details.vue
+141
-0
app/assets/javascripts/error_tracking/components/error_tracking_list.vue
...scripts/error_tracking/components/error_tracking_list.vue
+9
-8
app/assets/javascripts/error_tracking/components/stacktrace.vue
...sets/javascripts/error_tracking/components/stacktrace.vue
+33
-0
app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
...avascripts/error_tracking/components/stacktrace_entry.vue
+110
-0
app/assets/javascripts/error_tracking/details.js
app/assets/javascripts/error_tracking/details.js
+25
-0
app/assets/javascripts/error_tracking/list.js
app/assets/javascripts/error_tracking/list.js
+0
-0
app/assets/javascripts/error_tracking/services/index.js
app/assets/javascripts/error_tracking/services/index.js
+1
-1
app/assets/javascripts/error_tracking/store/details/actions.js
...ssets/javascripts/error_tracking/store/details/actions.js
+63
-0
app/assets/javascripts/error_tracking/store/details/getters.js
...ssets/javascripts/error_tracking/store/details/getters.js
+3
-0
app/assets/javascripts/error_tracking/store/details/mutation_types.js
...avascripts/error_tracking/store/details/mutation_types.js
+4
-0
app/assets/javascripts/error_tracking/store/details/mutations.js
...ets/javascripts/error_tracking/store/details/mutations.js
+16
-0
app/assets/javascripts/error_tracking/store/details/state.js
app/assets/javascripts/error_tracking/store/details/state.js
+6
-0
app/assets/javascripts/error_tracking/store/index.js
app/assets/javascripts/error_tracking/store/index.js
+25
-10
app/assets/javascripts/error_tracking/store/list/actions.js
app/assets/javascripts/error_tracking/store/list/actions.js
+2
-2
app/assets/javascripts/error_tracking/store/list/getters.js
app/assets/javascripts/error_tracking/store/list/getters.js
+0
-0
app/assets/javascripts/error_tracking/store/list/mutation_types.js
...s/javascripts/error_tracking/store/list/mutation_types.js
+0
-0
app/assets/javascripts/error_tracking/store/list/mutations.js
...assets/javascripts/error_tracking/store/list/mutations.js
+0
-0
app/assets/javascripts/error_tracking/store/list/state.js
app/assets/javascripts/error_tracking/store/list/state.js
+5
-0
app/assets/javascripts/pages/projects/error_tracking/details/index.js
...avascripts/pages/projects/error_tracking/details/index.js
+5
-0
app/assets/javascripts/pages/projects/error_tracking/index/index.js
.../javascripts/pages/projects/error_tracking/index/index.js
+5
-0
app/assets/stylesheets/pages/error_details.scss
app/assets/stylesheets/pages/error_details.scss
+18
-0
app/views/projects/error_tracking/details.html.haml
app/views/projects/error_tracking/details.html.haml
+2
-1
changelogs/unreleased/32464-detail-view-of-sentry-error.yml
changelogs/unreleased/32464-detail-view-of-sentry-error.yml
+5
-0
changelogs/unreleased/34258-embedding-sentry-stacktrace.yml
changelogs/unreleased/34258-embedding-sentry-stacktrace.yml
+5
-0
locale/gitlab.pot
locale/gitlab.pot
+21
-0
spec/frontend/error_tracking/components/error_details_spec.js
.../frontend/error_tracking/components/error_details_spec.js
+105
-0
spec/frontend/error_tracking/components/error_tracking_list_spec.js
...end/error_tracking/components/error_tracking_list_spec.js
+10
-5
spec/frontend/error_tracking/components/stacktrace_entry_spec.js
...ontend/error_tracking/components/stacktrace_entry_spec.js
+49
-0
spec/frontend/error_tracking/components/stacktrace_spec.js
spec/frontend/error_tracking/components/stacktrace_spec.js
+45
-0
spec/frontend/error_tracking/store/details/actions_spec.js
spec/frontend/error_tracking/store/details/actions_spec.js
+94
-0
spec/frontend/error_tracking/store/details/getters_spec.js
spec/frontend/error_tracking/store/details/getters_spec.js
+13
-0
spec/frontend/error_tracking/store/list/getters_spec.js
spec/frontend/error_tracking/store/list/getters_spec.js
+1
-1
spec/frontend/error_tracking/store/list/mutation_spec.js
spec/frontend/error_tracking/store/list/mutation_spec.js
+2
-2
No files found.
app/assets/javascripts/error_tracking/components/error_details.vue
0 → 100644
View file @
791794a0
<
script
>
import
{
mapActions
,
mapGetters
,
mapState
}
from
'
vuex
'
;
import
dateFormat
from
'
dateformat
'
;
import
{
__
,
sprintf
}
from
'
~/locale
'
;
import
{
GlButton
,
GlLink
,
GlLoadingIcon
}
from
'
@gitlab/ui
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
TooltipOnTruncate
from
'
~/vue_shared/components/tooltip_on_truncate.vue
'
;
import
Stacktrace
from
'
./stacktrace.vue
'
;
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
timeagoMixin
from
'
~/vue_shared/mixins/timeago
'
;
import
{
trackClickErrorLinkToSentryOptions
}
from
'
../utils
'
;
export
default
{
components
:
{
GlButton
,
GlLink
,
GlLoadingIcon
,
TooltipOnTruncate
,
Icon
,
Stacktrace
,
},
directives
:
{
TrackEvent
:
TrackEventDirective
,
},
mixins
:
[
timeagoMixin
],
props
:
{
issueDetailsPath
:
{
type
:
String
,
required
:
true
,
},
issueStackTracePath
:
{
type
:
String
,
required
:
true
,
},
},
computed
:
{
...
mapState
(
'
details
'
,
[
'
error
'
,
'
loading
'
,
'
loadingStacktrace
'
,
'
stacktraceData
'
]),
...
mapGetters
(
'
details
'
,
[
'
stacktrace
'
]),
reported
()
{
return
sprintf
(
__
(
'
Reported %{timeAgo} by %{reportedBy}
'
),
{
reportedBy
:
`<strong>
${
this
.
error
.
culprit
}
</strong>`
,
timeAgo
:
this
.
timeFormated
(
this
.
stacktraceData
.
date_received
),
},
false
,
);
},
firstReleaseLink
()
{
return
`
${
this
.
error
.
external_base_url
}
/releases/
${
this
.
error
.
first_release_short_version
}
`
;
},
lastReleaseLink
()
{
return
`
${
this
.
error
.
external_base_url
}
releases/
${
this
.
error
.
last_release_short_version
}
`
;
},
showDetails
()
{
return
Boolean
(
!
this
.
loading
&&
this
.
error
&&
this
.
error
.
id
);
},
showStacktrace
()
{
return
Boolean
(
!
this
.
loadingStacktrace
&&
this
.
stacktrace
&&
this
.
stacktrace
.
length
);
},
},
mounted
()
{
this
.
startPollingDetails
(
this
.
issueDetailsPath
);
this
.
startPollingStacktrace
(
this
.
issueStackTracePath
);
},
methods
:
{
...
mapActions
(
'
details
'
,
[
'
startPollingDetails
'
,
'
startPollingStacktrace
'
]),
trackClickErrorLinkToSentryOptions
,
formatDate
(
date
)
{
return
`
${
this
.
timeFormated
(
date
)}
(
${
dateFormat
(
date
,
'
UTC:yyyy-mm-dd h:MM:ssTT Z
'
)}
)`
;
},
},
};
</
script
>
<
template
>
<div>
<div
v-if=
"loading"
class=
"py-3"
>
<gl-loading-icon
:size=
"3"
/>
</div>
<div
v-else-if=
"showDetails"
class=
"error-details"
>
<div
class=
"top-area align-items-center justify-content-between py-3"
>
<span
v-if=
"!loadingStacktrace && stacktrace"
v-html=
"reported"
></span>
<!--
<gl-button
class=
"my-3 ml-auto"
variant=
"success"
>
{{
__
(
'
Create Issue
'
)
}}
</gl-button>
-->
</div>
<div>
<tooltip-on-truncate
:title=
"error.title"
truncate-target=
"child"
placement=
"top"
>
<h2
class=
"text-truncate"
>
{{
error
.
title
}}
</h2>
</tooltip-on-truncate>
<h3>
{{
__
(
'
Error details
'
)
}}
</h3>
<ul>
<li>
<span
class=
"bold"
>
{{
__
(
'
Sentry event
'
)
}}
:
</span>
<gl-link
v-track-event=
"trackClickErrorLinkToSentryOptions(error.external_url)"
:href=
"error.external_url"
target=
"_blank"
>
<span
class=
"text-truncate"
>
{{
error
.
external_url
}}
</span>
<icon
name=
"external-link"
class=
"ml-1 flex-shrink-0"
/>
</gl-link>
</li>
<li
v-if=
"error.first_release_short_version"
>
<span
class=
"bold"
>
{{
__
(
'
First seen
'
)
}}
:
</span>
{{
formatDate
(
error
.
first_seen
)
}}
<gl-link
:href=
"firstReleaseLink"
target=
"_blank"
>
<span>
{{
__
(
'
Release
'
)
}}
:
{{
error
.
first_release_short_version
}}
</span>
</gl-link>
</li>
<li
v-if=
"error.last_release_short_version"
>
<span
class=
"bold"
>
{{
__
(
'
Last seen
'
)
}}
:
</span>
{{
formatDate
(
error
.
last_seen
)
}}
<gl-link
:href=
"lastReleaseLink"
target=
"_blank"
>
<span>
{{
__
(
'
Release
'
)
}}
:
{{
error
.
last_release_short_version
}}
</span>
</gl-link>
</li>
<li>
<span
class=
"bold"
>
{{
__
(
'
Events
'
)
}}
:
</span>
<span>
{{
error
.
count
}}
</span>
</li>
<li>
<span
class=
"bold"
>
{{
__
(
'
Users
'
)
}}
:
</span>
<span>
{{
error
.
user_count
}}
</span>
</li>
</ul>
<div
v-if=
"loadingStacktrace"
class=
"py-3"
>
<gl-loading-icon
:size=
"3"
/>
</div>
<template
v-if=
"showStacktrace"
>
<h3
class=
"my-4"
>
{{
__
(
'
Stack trace
'
)
}}
</h3>
<stacktrace
:entries=
"stacktrace"
/>
</
template
>
</div>
</div>
</div>
</template>
app/assets/javascripts/error_tracking/components/error_tracking_list.vue
View file @
791794a0
...
...
@@ -8,11 +8,12 @@ import {
GlTable
,
GlSearchBoxByType
,
}
from
'
@gitlab/ui
'
;
import
{
visitUrl
}
from
'
~/lib/utils/url_utility
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
import
TimeAgo
from
'
~/vue_shared/components/time_ago_tooltip.vue
'
;
import
{
__
}
from
'
~/locale
'
;
import
TrackEventDirective
from
'
~/vue_shared/directives/track_event
'
;
import
{
trackViewInSentryOptions
,
trackClickErrorLinkToSentryOptions
}
from
'
../utils
'
;
import
{
trackViewInSentryOptions
}
from
'
../utils
'
;
export
default
{
fields
:
[
...
...
@@ -62,8 +63,8 @@ export default {
};
},
computed
:
{
...
mapState
([
'
errors
'
,
'
externalUrl
'
,
'
loading
'
]),
...
mapGetters
([
'
filterErrorsByTitle
'
]),
...
mapState
(
'
list
'
,
[
'
errors
'
,
'
externalUrl
'
,
'
loading
'
]),
...
mapGetters
(
'
list
'
,
[
'
filterErrorsByTitle
'
]),
filteredErrors
()
{
return
this
.
errorSearchQuery
?
this
.
filterErrorsByTitle
(
this
.
errorSearchQuery
)
:
this
.
errors
;
},
...
...
@@ -74,9 +75,11 @@ export default {
}
},
methods
:
{
...
mapActions
([
'
startPolling
'
,
'
restartPolling
'
]),
...
mapActions
(
'
list
'
,
[
'
startPolling
'
,
'
restartPolling
'
]),
trackViewInSentryOptions
,
trackClickErrorLinkToSentryOptions
,
viewDetails
(
errorId
)
{
visitUrl
(
`error_tracking/
${
errorId
}
/details`
);
},
},
};
</
script
>
...
...
@@ -125,13 +128,11 @@ export default {
<
template
slot=
"error"
slot-scope=
"errors"
>
<div
class=
"d-flex flex-column"
>
<gl-link
v-track-event=
"trackClickErrorLinkToSentryOptions(errors.item.externalUrl)"
:href=
"errors.item.externalUrl"
class=
"d-flex text-dark"
target=
"_blank"
@
click=
"viewDetails(errors.item.id)"
>
<strong
class=
"text-truncate"
>
{{
errors
.
item
.
title
.
trim
()
}}
</strong>
<icon
name=
"external-link"
class=
"ml-1 flex-shrink-0"
/>
</gl-link>
<span
class=
"text-secondary text-truncate"
>
{{
errors
.
item
.
culprit
}}
...
...
app/assets/javascripts/error_tracking/components/stacktrace.vue
0 → 100644
View file @
791794a0
<
script
>
import
StackTraceEntry
from
'
./stacktrace_entry.vue
'
;
export
default
{
components
:
{
StackTraceEntry
,
},
props
:
{
entries
:
{
type
:
Array
,
required
:
true
,
},
},
methods
:
{
isFirstEntry
(
index
)
{
return
index
===
0
;
},
},
};
</
script
>
<
template
>
<div
class=
"stacktrace"
>
<stack-trace-entry
v-for=
"(entry, index) in entries"
:key=
"`stacktrace-entry-$
{index}`"
:lines="entry.context"
:file-path="entry.filename"
:error-line="entry.lineNo"
:expanded="isFirstEntry(index)"
/>
</div>
</
template
>
app/assets/javascripts/error_tracking/components/stacktrace_entry.vue
0 → 100644
View file @
791794a0
<
script
>
import
{
GlTooltip
}
from
'
@gitlab/ui
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
FileIcon
from
'
~/vue_shared/components/file_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
export
default
{
components
:
{
ClipboardButton
,
FileIcon
,
Icon
,
},
directives
:
{
GlTooltip
,
},
props
:
{
lines
:
{
type
:
Array
,
required
:
true
,
},
filePath
:
{
type
:
String
,
required
:
true
,
},
errorLine
:
{
type
:
Number
,
required
:
true
,
},
expanded
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
data
()
{
return
{
isExpanded
:
this
.
expanded
,
};
},
computed
:
{
linesLength
()
{
return
this
.
lines
.
length
;
},
collapseIcon
()
{
return
this
.
isExpanded
?
'
chevron-down
'
:
'
chevron-right
'
;
},
},
methods
:
{
isHighlighted
(
lineNum
)
{
return
lineNum
===
this
.
errorLine
;
},
toggle
()
{
this
.
isExpanded
=
!
this
.
isExpanded
;
},
lineNum
(
line
)
{
return
line
[
0
];
},
lineCode
(
line
)
{
return
line
[
1
];
},
},
userColorScheme
:
window
.
gon
.
user_color_scheme
,
};
</
script
>
<
template
>
<div
class=
"file-holder"
>
<div
ref=
"header"
class=
"file-title file-title-flex-parent"
>
<div
class=
"file-header-content "
>
<div
class=
"d-inline-block cursor-pointer"
@
click=
"toggle()"
>
<icon
:name=
"collapseIcon"
:size=
"16"
aria-hidden=
"true"
class=
"append-right-5"
/>
</div>
<div
class=
"d-inline-block append-right-4"
>
<file-icon
:file-name=
"filePath"
:size=
"18"
aria-hidden=
"true"
css-classes=
"append-right-5"
/>
<strong
v-gl-tooltip
:title=
"filePath"
class=
"file-title-name"
data-container=
"body"
>
{{
filePath
}}
</strong>
</div>
<clipboard-button
:title=
"__('Copy file path')"
:text=
"filePath"
css-class=
"btn-default btn-transparent btn-clipboard"
/>
</div>
</div>
<table
v-if=
"isExpanded"
:class=
"$options.userColorScheme"
class=
"code js-syntax-highlight"
>
<tbody>
<template
v-for=
"(line, index) in lines"
>
<tr
:key=
"`stacktrace-line-$
{index}`" class="line_holder">
<td
class=
"diff-line-num"
:class=
"
{ old: isHighlighted(lineNum(line)) }">
{{
lineNum
(
line
)
}}
</td>
<td
class=
"line_content"
:class=
"
{ old: isHighlighted(lineNum(line)) }"
v-html="lineCode(line)"
>
</td>
</tr>
</
template
>
</tbody>
</table>
</div>
</template>
app/assets/javascripts/error_tracking/details.js
0 → 100644
View file @
791794a0
import
Vue
from
'
vue
'
;
import
store
from
'
./store
'
;
import
ErrorDetails
from
'
./components/error_details.vue
'
;
export
default
()
=>
{
// eslint-disable-next-line no-new
new
Vue
({
el
:
'
#js-error_details
'
,
components
:
{
ErrorDetails
,
},
store
,
render
(
createElement
)
{
const
domEl
=
document
.
querySelector
(
this
.
$options
.
el
);
const
{
issueDetailsPath
,
issueStackTracePath
}
=
domEl
.
dataset
;
return
createElement
(
'
error-details
'
,
{
props
:
{
issueDetailsPath
,
issueStackTracePath
,
},
});
},
});
};
app/assets/javascripts/error_tracking/
index
.js
→
app/assets/javascripts/error_tracking/
list
.js
View file @
791794a0
File moved
app/assets/javascripts/error_tracking/services/index.js
View file @
791794a0
import
axios
from
'
~/lib/utils/axios_utils
'
;
export
default
{
get
ErrorList
({
endpoint
})
{
get
SentryData
({
endpoint
})
{
return
axios
.
get
(
endpoint
);
},
};
app/assets/javascripts/error_tracking/store/details/actions.js
0 → 100644
View file @
791794a0
import
service
from
'
../../services
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
createFlash
from
'
~/flash
'
;
import
Poll
from
'
~/lib/utils/poll
'
;
import
{
__
}
from
'
~/locale
'
;
let
stackTracePoll
;
let
detailPoll
;
const
stopPolling
=
poll
=>
{
if
(
poll
)
poll
.
stop
();
};
export
function
startPollingDetails
({
commit
},
endpoint
)
{
detailPoll
=
new
Poll
({
resource
:
service
,
method
:
'
getSentryData
'
,
data
:
{
endpoint
},
successCallback
:
({
data
})
=>
{
if
(
!
data
)
{
detailPoll
.
restart
();
return
;
}
commit
(
types
.
SET_ERROR
,
data
.
error
);
commit
(
types
.
SET_LOADING
,
false
);
stopPolling
(
detailPoll
);
},
errorCallback
:
()
=>
{
commit
(
types
.
SET_LOADING
,
false
);
createFlash
(
__
(
'
Failed to load error details from Sentry.
'
));
},
});
detailPoll
.
makeRequest
();
}
export
function
startPollingStacktrace
({
commit
},
endpoint
)
{
stackTracePoll
=
new
Poll
({
resource
:
service
,
method
:
'
getSentryData
'
,
data
:
{
endpoint
},
successCallback
:
({
data
})
=>
{
if
(
!
data
)
{
stackTracePoll
.
restart
();
return
;
}
commit
(
types
.
SET_STACKTRACE_DATA
,
data
.
error
);
commit
(
types
.
SET_LOADING_STACKTRACE
,
false
);
stopPolling
(
stackTracePoll
);
},
errorCallback
:
()
=>
{
commit
(
types
.
SET_LOADING_STACKTRACE
,
false
);
createFlash
(
__
(
'
Failed to load stacktrace.
'
));
},
});
stackTracePoll
.
makeRequest
();
}
export
default
()
=>
{};
app/assets/javascripts/error_tracking/store/details/getters.js
0 → 100644
View file @
791794a0
export
const
stacktrace
=
state
=>
state
.
stacktraceData
.
stack_trace_entries
.
reverse
();
export
default
()
=>
{};
app/assets/javascripts/error_tracking/store/details/mutation_types.js
0 → 100644
View file @
791794a0
export
const
SET_ERROR
=
'
SET_ERRORS
'
;
export
const
SET_LOADING
=
'
SET_LOADING
'
;
export
const
SET_LOADING_STACKTRACE
=
'
SET_LOADING_STACKTRACE
'
;
export
const
SET_STACKTRACE_DATA
=
'
SET_STACKTRACE_DATA
'
;
app/assets/javascripts/error_tracking/store/details/mutations.js
0 → 100644
View file @
791794a0
import
*
as
types
from
'
./mutation_types
'
;
export
default
{
[
types
.
SET_ERROR
](
state
,
data
)
{
state
.
error
=
data
;
},
[
types
.
SET_LOADING
](
state
,
loading
)
{
state
.
loading
=
loading
;
},
[
types
.
SET_LOADING_STACKTRACE
](
state
,
data
)
{
state
.
loadingStacktrace
=
data
;
},
[
types
.
SET_STACKTRACE_DATA
](
state
,
data
)
{
state
.
stacktraceData
=
data
;
},
};
app/assets/javascripts/error_tracking/store/details/state.js
0 → 100644
View file @
791794a0
export
default
()
=>
({
error
:
{},
stacktraceData
:
{},
loading
:
true
,
loadingStacktrace
:
true
,
});
app/assets/javascripts/error_tracking/store/index.js
View file @
791794a0
import
Vue
from
'
vue
'
;
import
Vuex
from
'
vuex
'
;
import
*
as
actions
from
'
./actions
'
;
import
*
as
getters
from
'
./getters
'
;
import
mutations
from
'
./mutations
'
;
import
*
as
listActions
from
'
./list/actions
'
;
import
listMutations
from
'
./list/mutations
'
;
import
listState
from
'
./list/state
'
;
import
*
as
listGetters
from
'
./list/getters
'
;
import
*
as
detailsActions
from
'
./details/actions
'
;
import
detailsMutations
from
'
./details/mutations
'
;
import
detailsState
from
'
./details/state
'
;
import
*
as
detailsGetters
from
'
./details/getters
'
;
Vue
.
use
(
Vuex
);
export
const
createStore
=
()
=>
new
Vuex
.
Store
({
state
:
{
errors
:
[],
externalUrl
:
''
,
loading
:
true
,
modules
:
{
list
:
{
namespaced
:
true
,
state
:
listState
(),
actions
:
listActions
,
mutations
:
listMutations
,
getters
:
listGetters
,
},
details
:
{
namespaced
:
true
,
state
:
detailsState
(),
actions
:
detailsActions
,
mutations
:
detailsMutations
,
getters
:
detailsGetters
,
},
},
actions
,
mutations
,
getters
,
});
export
default
createStore
();
app/assets/javascripts/error_tracking/store/actions.js
→
app/assets/javascripts/error_tracking/store/
list/
actions.js
View file @
791794a0
import
Service
from
'
../services
'
;
import
Service
from
'
../
../
services
'
;
import
*
as
types
from
'
./mutation_types
'
;
import
createFlash
from
'
~/flash
'
;
import
Poll
from
'
~/lib/utils/poll
'
;
...
...
@@ -9,7 +9,7 @@ let eTagPoll;
export
function
startPolling
({
commit
,
dispatch
},
endpoint
)
{
eTagPoll
=
new
Poll
({
resource
:
Service
,
method
:
'
get
ErrorList
'
,
method
:
'
get
SentryData
'
,
data
:
{
endpoint
},
successCallback
:
({
data
})
=>
{
if
(
!
data
)
{
...
...
app/assets/javascripts/error_tracking/store/getters.js
→
app/assets/javascripts/error_tracking/store/
list/
getters.js
View file @
791794a0
File moved
app/assets/javascripts/error_tracking/store/mutation_types.js
→
app/assets/javascripts/error_tracking/store/
list/
mutation_types.js
View file @
791794a0
File moved
app/assets/javascripts/error_tracking/store/mutations.js
→
app/assets/javascripts/error_tracking/store/
list/
mutations.js
View file @
791794a0
File moved
app/assets/javascripts/error_tracking/store/list/state.js
0 → 100644
View file @
791794a0
export
default
()
=>
({
errors
:
[],
externalUrl
:
''
,
loading
:
true
,
});
app/assets/javascripts/pages/projects/error_tracking/details/index.js
0 → 100644
View file @
791794a0
import
ErrorTrackingDetails
from
'
~/error_tracking/details
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
ErrorTrackingDetails
();
});
app/assets/javascripts/pages/projects/error_tracking/index.js
→
app/assets/javascripts/pages/projects/error_tracking/index
/index
.js
View file @
791794a0
import
ErrorTracking
from
'
~/error_tracking
'
;
import
ErrorTracking
List
from
'
~/error_tracking/list
'
;
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
ErrorTracking
();
ErrorTracking
List
();
});
app/assets/stylesheets/pages/error_details.scss
0 → 100644
View file @
791794a0
.error-details
{
li
{
@include
gl-line-height-32
;
}
}
.stacktrace
{
.file-title
{
svg
{
vertical-align
:
middle
;
top
:
-1px
;
}
}
.
line_content
.
old
:
:
before
{
content
:
none
!
important
;
}
}
app/views/projects/error_tracking/details.html.haml
View file @
791794a0
-
page_title
_
(
'Error Details'
)
-
add_to_breadcrumbs
'Errors'
,
project_error_tracking_index_path
(
@project
)
#js-error_
tracking
{
data:
error_details_data
(
@current_user
,
@project
)
}
#js-error_
details
{
data:
error_details_data
(
@current_user
,
@project
)
}
changelogs/unreleased/32464-detail-view-of-sentry-error.yml
0 → 100644
View file @
791794a0
---
title
:
Detail view of Sentry error in GitLab
merge_request
:
18878
author
:
type
:
added
changelogs/unreleased/34258-embedding-sentry-stacktrace.yml
0 → 100644
View file @
791794a0
---
title
:
Sentry error stacktrace
merge_request
:
19492
author
:
type
:
added
locale/gitlab.pot
View file @
791794a0
...
...
@@ -6625,6 +6625,9 @@ msgstr ""
msgid "Error deleting %{issuableType}"
msgstr ""
msgid "Error details"
msgstr ""
msgid "Error fetching diverging counts for branches. Please try again."
msgstr ""
...
...
@@ -7057,6 +7060,9 @@ msgstr ""
msgid "Failed to load emoji list."
msgstr ""
msgid "Failed to load error details from Sentry."
msgstr ""
msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
...
...
@@ -7066,6 +7072,9 @@ msgstr ""
msgid "Failed to load related branches"
msgstr ""
msgid "Failed to load stacktrace."
msgstr ""
msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
msgstr ""
...
...
@@ -7455,6 +7464,9 @@ msgstr ""
msgid "First name"
msgstr ""
msgid "First seen"
msgstr ""
msgid "Fixed date"
msgstr ""
...
...
@@ -14222,6 +14234,9 @@ msgstr ""
msgid "Report abuse to admin"
msgstr ""
msgid "Reported %{timeAgo} by %{reportedBy}"
msgstr ""
msgid "Reporting"
msgstr ""
...
...
@@ -15198,6 +15213,9 @@ msgstr ""
msgid "Sentry API URL"
msgstr ""
msgid "Sentry event"
msgstr ""
msgid "Sep"
msgstr ""
...
...
@@ -16022,6 +16040,9 @@ msgstr ""
msgid "Squash commits"
msgstr ""
msgid "Stack trace"
msgstr ""
msgid "Stage"
msgstr ""
...
...
spec/frontend/error_tracking/components/error_details_spec.js
0 → 100644
View file @
791794a0
import
{
createLocalVue
,
shallowMount
}
from
'
@vue/test-utils
'
;
import
Vuex
from
'
vuex
'
;
import
{
GlLoadingIcon
,
GlLink
}
from
'
@gitlab/ui
'
;
import
Stacktrace
from
'
~/error_tracking/components/stacktrace.vue
'
;
import
ErrorDetails
from
'
~/error_tracking/components/error_details.vue
'
;
const
localVue
=
createLocalVue
();
localVue
.
use
(
Vuex
);
describe
(
'
ErrorDetails
'
,
()
=>
{
let
store
;
let
wrapper
;
let
actions
;
let
getters
;
function
mountComponent
()
{
wrapper
=
shallowMount
(
ErrorDetails
,
{
localVue
,
store
,
propsData
:
{
issueDetailsPath
:
'
/123/details
'
,
issueStackTracePath
:
'
/stacktrace
'
,
},
});
}
beforeEach
(()
=>
{
actions
=
{
startPollingDetails
:
()
=>
{},
startPollingStacktrace
:
()
=>
{},
};
getters
=
{
sentryUrl
:
()
=>
'
sentry.io
'
,
stacktrace
:
()
=>
[{
context
:
[
1
,
2
],
lineNo
:
53
,
filename
:
'
index.js
'
}],
};
const
state
=
{
error
:
{},
loading
:
true
,
stacktraceData
:
{},
loadingStacktrace
:
true
,
};
store
=
new
Vuex
.
Store
({
modules
:
{
details
:
{
namespaced
:
true
,
actions
,
state
,
getters
,
},
},
});
});
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
describe
(
'
loading
'
,
()
=>
{
beforeEach
(()
=>
{
mountComponent
();
});
it
(
'
should show spinner while loading
'
,
()
=>
{
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
GlLink
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
Stacktrace
).
exists
()).
toBe
(
false
);
});
});
describe
(
'
Error details
'
,
()
=>
{
it
(
'
should show Sentry error details without stacktrace
'
,
()
=>
{
store
.
state
.
details
.
loading
=
false
;
store
.
state
.
details
.
error
.
id
=
1
;
mountComponent
();
expect
(
wrapper
.
find
(
GlLink
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
Stacktrace
).
exists
()).
toBe
(
false
);
});
describe
(
'
Stacktrace
'
,
()
=>
{
it
(
'
should show stacktrace
'
,
()
=>
{
store
.
state
.
details
.
loading
=
false
;
store
.
state
.
details
.
error
.
id
=
1
;
store
.
state
.
details
.
loadingStacktrace
=
false
;
mountComponent
();
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
Stacktrace
).
exists
()).
toBe
(
true
);
});
it
(
'
should NOT show stacktrace if no entries
'
,
()
=>
{
store
.
state
.
details
.
loading
=
false
;
store
.
state
.
details
.
loadingStacktrace
=
false
;
store
.
getters
=
{
'
details/sentryUrl
'
:
()
=>
'
sentry.io
'
,
'
details/stacktrace
'
:
()
=>
[]
};
mountComponent
();
expect
(
wrapper
.
find
(
GlLoadingIcon
).
exists
()).
toBe
(
false
);
expect
(
wrapper
.
find
(
Stacktrace
).
exists
()).
toBe
(
false
);
});
});
});
});
spec/frontend/error_tracking/components/error_tracking_list_spec.js
View file @
791794a0
...
...
@@ -34,7 +34,7 @@ describe('ErrorTrackingList', () => {
beforeEach
(()
=>
{
actions
=
{
get
ErrorList
:
()
=>
{},
get
SentryData
:
()
=>
{},
startPolling
:
()
=>
{},
restartPolling
:
jest
.
fn
().
mockName
(
'
restartPolling
'
),
};
...
...
@@ -45,8 +45,13 @@ describe('ErrorTrackingList', () => {
};
store
=
new
Vuex
.
Store
({
modules
:
{
list
:
{
namespaced
:
true
,
actions
,
state
,
},
},
});
});
...
...
@@ -70,7 +75,7 @@ describe('ErrorTrackingList', () => {
describe
(
'
results
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
loading
=
false
;
store
.
state
.
l
ist
.
l
oading
=
false
;
mountComponent
();
});
...
...
@@ -84,7 +89,7 @@ describe('ErrorTrackingList', () => {
describe
(
'
no results
'
,
()
=>
{
beforeEach
(()
=>
{
store
.
state
.
loading
=
false
;
store
.
state
.
l
ist
.
l
oading
=
false
;
mountComponent
();
});
...
...
spec/frontend/error_tracking/components/stacktrace_entry_spec.js
0 → 100644
View file @
791794a0
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
StackTraceEntry
from
'
~/error_tracking/components/stacktrace_entry.vue
'
;
import
ClipboardButton
from
'
~/vue_shared/components/clipboard_button.vue
'
;
import
FileIcon
from
'
~/vue_shared/components/file_icon.vue
'
;
import
Icon
from
'
~/vue_shared/components/icon.vue
'
;
describe
(
'
Stacktrace Entry
'
,
()
=>
{
let
wrapper
;
function
mountComponent
(
props
)
{
wrapper
=
shallowMount
(
StackTraceEntry
,
{
propsData
:
{
filePath
:
'
sidekiq/util.rb
'
,
lines
:
[
[
22
,
'
def safe_thread(name,
\
u0026block)
\n
'
],
[
23
,
'
Thread.new do
\n
'
],
[
24
,
"
Thread.current['sidekiq_label'] = name
\n
"
],
[
25
,
'
watchdog(name,
\
u0026block)
\n
'
],
],
errorLine
:
24
,
...
props
,
},
});
}
beforeEach
(()
=>
{
mountComponent
();
});
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
it
(
'
should render stacktrace entry collapsed
'
,
()
=>
{
expect
(
wrapper
.
find
(
StackTraceEntry
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
ClipboardButton
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
Icon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
find
(
FileIcon
).
exists
()).
toBe
(
true
);
expect
(
wrapper
.
element
.
querySelectorAll
(
'
table
'
).
length
).
toBe
(
0
);
});
it
(
'
should render stacktrace entry table expanded
'
,
()
=>
{
mountComponent
({
expanded
:
true
});
expect
(
wrapper
.
element
.
querySelectorAll
(
'
tr.line_holder
'
).
length
).
toBe
(
4
);
expect
(
wrapper
.
element
.
querySelectorAll
(
'
.line_content.old
'
).
length
).
toBe
(
1
);
});
});
spec/frontend/error_tracking/components/stacktrace_spec.js
0 → 100644
View file @
791794a0
import
{
shallowMount
}
from
'
@vue/test-utils
'
;
import
Stacktrace
from
'
~/error_tracking/components/stacktrace.vue
'
;
import
StackTraceEntry
from
'
~/error_tracking/components/stacktrace_entry.vue
'
;
describe
(
'
ErrorDetails
'
,
()
=>
{
let
wrapper
;
const
stackTraceEntry
=
{
filename
:
'
sidekiq/util.rb
'
,
context
:
[
[
22
,
'
def safe_thread(name,
\
u0026block)
\n
'
],
[
23
,
'
Thread.new do
\n
'
],
[
24
,
"
Thread.current['sidekiq_label'] = name
\n
"
],
[
25
,
'
watchdog(name,
\
u0026block)
\n
'
],
],
lineNo
:
24
,
};
function
mountComponent
(
entries
)
{
wrapper
=
shallowMount
(
Stacktrace
,
{
propsData
:
{
entries
,
},
});
}
describe
(
'
Stacktrace
'
,
()
=>
{
afterEach
(()
=>
{
if
(
wrapper
)
{
wrapper
.
destroy
();
}
});
it
(
'
should render single Stacktrace entry
'
,
()
=>
{
mountComponent
([
stackTraceEntry
]);
expect
(
wrapper
.
findAll
(
StackTraceEntry
).
length
).
toBe
(
1
);
});
it
(
'
should render multiple Stacktrace entry
'
,
()
=>
{
const
entriesNum
=
3
;
mountComponent
(
new
Array
(
entriesNum
).
fill
(
stackTraceEntry
));
expect
(
wrapper
.
findAll
(
StackTraceEntry
).
length
).
toBe
(
entriesNum
);
});
});
});
spec/frontend/error_tracking/store/details/actions_spec.js
0 → 100644
View file @
791794a0
import
axios
from
'
~/lib/utils/axios_utils
'
;
import
MockAdapter
from
'
axios-mock-adapter
'
;
import
testAction
from
'
helpers/vuex_action_helper
'
;
import
createFlash
from
'
~/flash
'
;
import
*
as
actions
from
'
~/error_tracking/store/details/actions
'
;
import
*
as
types
from
'
~/error_tracking/store/details/mutation_types
'
;
jest
.
mock
(
'
~/flash.js
'
);
let
mock
;
describe
(
'
Sentry error details store actions
'
,
()
=>
{
beforeEach
(()
=>
{
mock
=
new
MockAdapter
(
axios
);
});
afterEach
(()
=>
{
mock
.
restore
();
createFlash
.
mockClear
();
});
describe
(
'
startPollingDetails
'
,
()
=>
{
const
endpoint
=
'
123/details
'
;
it
(
'
should commit SET_ERROR with received response
'
,
done
=>
{
const
payload
=
{
error
:
{
id
:
1
}
};
mock
.
onGet
().
reply
(
200
,
payload
);
testAction
(
actions
.
startPollingDetails
,
{
endpoint
},
{},
[
{
type
:
types
.
SET_ERROR
,
payload
:
payload
.
error
},
{
type
:
types
.
SET_LOADING
,
payload
:
false
},
],
[],
()
=>
{
done
();
},
);
});
it
(
'
should show flash on API error
'
,
done
=>
{
mock
.
onGet
().
reply
(
400
);
testAction
(
actions
.
startPollingDetails
,
{
endpoint
},
{},
[{
type
:
types
.
SET_LOADING
,
payload
:
false
}],
[],
()
=>
{
expect
(
createFlash
).
toHaveBeenCalledTimes
(
1
);
done
();
},
);
});
});
describe
(
'
startPollingStacktrace
'
,
()
=>
{
const
endpoint
=
'
123/stacktrace
'
;
it
(
'
should commit SET_ERROR with received response
'
,
done
=>
{
const
payload
=
{
error
:
[
1
,
2
,
3
]
};
mock
.
onGet
().
reply
(
200
,
payload
);
testAction
(
actions
.
startPollingStacktrace
,
{
endpoint
},
{},
[
{
type
:
types
.
SET_STACKTRACE_DATA
,
payload
:
payload
.
error
},
{
type
:
types
.
SET_LOADING_STACKTRACE
,
payload
:
false
},
],
[],
()
=>
{
done
();
},
);
});
it
(
'
should show flash on API error
'
,
done
=>
{
mock
.
onGet
().
reply
(
400
);
testAction
(
actions
.
startPollingStacktrace
,
{
endpoint
},
{},
[{
type
:
types
.
SET_LOADING_STACKTRACE
,
payload
:
false
}],
[],
()
=>
{
expect
(
createFlash
).
toHaveBeenCalledTimes
(
1
);
done
();
},
);
});
});
});
spec/frontend/error_tracking/store/details/getters_spec.js
0 → 100644
View file @
791794a0
import
*
as
getters
from
'
~/error_tracking/store/details/getters
'
;
describe
(
'
Sentry error details store getters
'
,
()
=>
{
const
state
=
{
stacktraceData
:
{
stack_trace_entries
:
[
1
,
2
]
},
};
describe
(
'
stacktrace
'
,
()
=>
{
it
(
'
should get stacktrace
'
,
()
=>
{
expect
(
getters
.
stacktrace
(
state
)).
toEqual
([
2
,
1
]);
});
});
});
spec/frontend/error_tracking/store/getters_spec.js
→
spec/frontend/error_tracking/store/
list/
getters_spec.js
View file @
791794a0
import
*
as
getters
from
'
~/error_tracking/store/getters
'
;
import
*
as
getters
from
'
~/error_tracking/store/
list/
getters
'
;
describe
(
'
Error Tracking getters
'
,
()
=>
{
let
state
;
...
...
spec/frontend/error_tracking/store/mutation_spec.js
→
spec/frontend/error_tracking/store/
list/
mutation_spec.js
View file @
791794a0
import
mutations
from
'
~/error_tracking/store/mutations
'
;
import
*
as
types
from
'
~/error_tracking/store/mutation_types
'
;
import
mutations
from
'
~/error_tracking/store/
list/
mutations
'
;
import
*
as
types
from
'
~/error_tracking/store/
list/
mutation_types
'
;
describe
(
'
Error tracking mutations
'
,
()
=>
{
describe
(
'
SET_ERRORS
'
,
()
=>
{
...
...
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