Skip to content
Snippets Groups Projects
Commit 7071f9bf authored by GitLab Bot's avatar GitLab Bot
Browse files

Add latest changes from gitlab-org/gitlab@master

parent 16bd8409
No related branches found
No related tags found
No related merge requests found
Showing
with 453 additions and 21 deletions
<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>
Loading
Loading
@@ -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: [
Loading
Loading
@@ -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;
},
Loading
Loading
@@ -74,9 +75,11 @@ export default {
}
},
methods: {
...mapActions(['startPolling', 'restartPolling']),
...mapActions('list', ['startPolling', 'restartPolling']),
trackViewInSentryOptions,
trackClickErrorLinkToSentryOptions,
viewDetails(errorId) {
visitUrl(`error_tracking/${errorId}/details`);
},
},
};
</script>
Loading
Loading
@@ -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 }}
Loading
Loading
<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>
<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>
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,
},
});
},
});
};
import axios from '~/lib/utils/axios_utils';
 
export default {
getErrorList({ endpoint }) {
getSentryData({ endpoint }) {
return axios.get(endpoint);
},
};
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 () => {};
export const stacktrace = state => state.stacktraceData.stack_trace_entries.reverse();
export default () => {};
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';
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;
},
};
export default () => ({
error: {},
stacktraceData: {},
loading: true,
loadingStacktrace: true,
});
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();
import Service from '../services';
import Service from '../../services';
import * as types from './mutation_types';
import createFlash from '~/flash';
import Poll from '~/lib/utils/poll';
Loading
Loading
@@ -9,7 +9,7 @@ let eTagPoll;
export function startPolling({ commit, dispatch }, endpoint) {
eTagPoll = new Poll({
resource: Service,
method: 'getErrorList',
method: 'getSentryData',
data: { endpoint },
successCallback: ({ data }) => {
if (!data) {
Loading
Loading
export default () => ({
errors: [],
externalUrl: '',
loading: true,
});
import ErrorTrackingDetails from '~/error_tracking/details';
document.addEventListener('DOMContentLoaded', () => {
ErrorTrackingDetails();
});
import ErrorTracking from '~/error_tracking';
import ErrorTrackingList from '~/error_tracking/list';
 
document.addEventListener('DOMContentLoaded', () => {
ErrorTracking();
ErrorTrackingList();
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment