Commit d8ba5d5a authored by lauraMon's avatar lauraMon Committed by Martin Wortschack

Refactors and adds changelog

* Adds support for plurals, and updates the international text
* Adds a disabled state for button once its clicked
* Adds a spec to test submit
* Some more function refactors
parent 4b544817
<script>
import { mapActions, mapGetters, mapState } from 'vuex';
import dateFormat from 'dateformat';
import { __, sprintf } from '~/locale';
import { GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { __, sprintf, n__ } from '~/locale';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import Stacktrace from './stacktrace.vue';
......@@ -12,7 +13,8 @@ import { trackClickErrorLinkToSentryOptions } from '../utils';
export default {
components: {
GlButton,
LoadingButton,
GlFormInput,
GlLink,
GlLoadingIcon,
TooltipOnTruncate,
......@@ -41,6 +43,11 @@ export default {
required: true,
},
},
data() {
return {
issueCreationInProgress: false,
};
},
computed: {
...mapState('details', ['error', 'loading', 'loadingStacktrace', 'stacktraceData']),
...mapGetters('details', ['stacktrace']),
......@@ -67,46 +74,22 @@ export default {
return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length);
},
issueTitle() {
return `${this.error.title}`;
},
errorUrl() {
return sprintf(
__('- Sentry Event: %{external_url}'),
{
external_url: `${this.error.external_url}\n`,
},
false,
);
},
errorFirstSeen() {
return sprintf(
__('- First seen: %{first_seen}'),
{ first_seen: `${this.error.first_seen}\n` },
false,
);
},
errorLastSeen() {
return sprintf(
__('- Last seen: %{last_seen}'),
{ last_seen: `${this.error.last_seen}\n` },
false,
);
},
errorCount() {
return sprintf(__('- Events: %{count}'), { count: `${this.error.count}\n` }, false);
},
errorUserCount() {
return sprintf(
__('- Users: %{user_count}'),
{ user_count: `${this.error.user_count}\n` },
false,
);
return this.error.title;
},
issueDescription() {
return sprintf(
__('## Error Details: %{description}'),
__(
'%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}',
),
{
description: `\n${this.errorUrl}${this.errorFirstSeen}${this.errorLastSeen}${this.errorCount}${this.errorUserCount}`,
description: '# Error Details:\n',
errorUrl: `${this.error.external_url}\n`,
firstSeen: `\n${this.error.first_seen}\n`,
lastSeen: `${this.error.last_seen}\n`,
countLabel: n__('- Event', '- Events', this.error.count),
count: `${this.error.count}\n`,
userCountLabel: n__('- User', '- Users', this.error.user_count),
userCount: `${this.error.user_count}\n`,
},
false,
);
......@@ -120,6 +103,7 @@ export default {
...mapActions('details', ['startPollingDetails', 'startPollingStacktrace']),
trackClickErrorLinkToSentryOptions,
createIssue() {
this.issueCreationInProgress = true;
this.$refs.sentryIssueForm.submit();
},
formatDate(date) {
......@@ -139,12 +123,15 @@ export default {
<div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
<form ref="sentryIssueForm" :action="projectIssuesPath" method="POST">
<input name="issue[title]" :value="issueTitle" type="hidden" />
<gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
<input name="issue[description]" :value="issueDescription" type="hidden" />
<input :value="csrfToken" type="hidden" name="authenticity_token" />
<gl-button variant="success" @click="createIssue">
{{ __('Create issue') }}
</gl-button>
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
<loading-button
class="btn-success"
:label="__('Create issue')"
:loading="issueCreationInProgress"
@click="createIssue"
/>
</form>
</div>
<div>
......
---
title: Adds ability to create issues from sentry details page
merge_request: 20666
author:
type: added
......@@ -65,9 +65,6 @@ msgstr ""
msgid "\"%{path}\" did not exist on \"%{ref}\""
msgstr ""
msgid "## Error Details: %{description}"
msgstr ""
msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] ""
......@@ -237,6 +234,9 @@ msgstr[1] ""
msgid "%{count} related %{pluralized_subject}: %{links}"
msgstr ""
msgid "%{description}- Sentry event: %{errorUrl}- First seen: %{firstSeen}- Last seen: %{lastSeen} %{countLabel}: %{count}%{userCountLabel}: %{userCount}"
msgstr ""
msgid "%{duration}ms"
msgstr ""
......@@ -477,14 +477,10 @@ msgstr ""
msgid ", or "
msgstr ""
msgid "- Events: %{count}"
msgstr ""
msgid "- First seen: %{first_seen}"
msgstr ""
msgid "- Last seen: %{last_seen}"
msgstr ""
msgid "- Event"
msgid_plural "- Events"
msgstr[0] ""
msgstr[1] ""
msgid "- Runner is active and can process any new jobs"
msgstr ""
......@@ -492,11 +488,10 @@ msgstr ""
msgid "- Runner is paused and will not receive any new jobs"
msgstr ""
msgid "- Sentry Event: %{external_url}"
msgstr ""
msgid "- Users: %{user_count}"
msgstr ""
msgid "- User"
msgid_plural "- Users"
msgstr[0] ""
msgstr[1] ""
msgid "- show less"
msgstr ""
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { GlLoadingIcon, GlLink } from '@gitlab/ui';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { GlFormInput, GlLoadingIcon, GlLink } from '@gitlab/ui';
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
import ErrorDetails from '~/error_tracking/components/error_details.vue';
......@@ -15,6 +16,7 @@ describe('ErrorDetails', () => {
function mountComponent() {
wrapper = shallowMount(ErrorDetails, {
stubs: { LoadingButton },
localVue,
store,
propsData: {
......@@ -84,7 +86,28 @@ describe('ErrorDetails', () => {
expect(wrapper.find(Stacktrace).exists()).toBe(false);
});
it('should create an issue with title and description', () => {
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);
});
});
describe('When a user clicks the create issue button', () => {
beforeEach(() => {
store.state.details.loading = false;
store.state.details.error = {
id: 1,
......@@ -96,35 +119,23 @@ describe('ErrorDetails', () => {
user_count: 2,
};
mountComponent();
});
const form = wrapper.find({ ref: 'sentryIssueForm' });
const csrfTokenInput = wrapper.find('input[name="authenticity_token"]');
const issueTitleInput = wrapper.find('input[name="issue[title]"]');
it('should set the form values with title and description', () => {
const csrfTokenInput = wrapper.find('glforminput-stub[name="authenticity_token"]');
const issueTitleInput = wrapper.find('glforminput-stub[name="issue[title]"]');
const issueDescriptionInput = wrapper.find('input[name="issue[description]"]');
expect(form).toExist();
expect(csrfTokenInput.attributes('value')).toBe('fakeToken');
expect(issueTitleInput.attributes('value')).toContain(wrapper.vm.issueTitle);
expect(issueDescriptionInput.attributes('value')).toContain(wrapper.vm.issueDescription);
});
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);
it('should submit the form', () => {
window.HTMLFormElement.prototype.submit = () => {};
const submitSpy = jest.spyOn(wrapper.vm.$refs.sentryIssueForm, 'submit');
wrapper.find('button').trigger('click');
expect(submitSpy).toHaveBeenCalled();
submitSpy.mockRestore();
});
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment