Commit 8ceaebc7 authored by Dmitriy Zaporozhets (DZ)'s avatar Dmitriy Zaporozhets (DZ) Committed by Mayra Cabrera

Add GitLab DSN to error tracking UI

parent 5c5d8ad4
<script> <script>
import { GlButton, GlFormGroup, GlFormCheckbox, GlFormRadioGroup, GlFormRadio } from '@gitlab/ui'; import {
GlButton,
GlFormGroup,
GlFormCheckbox,
GlFormRadioGroup,
GlFormRadio,
GlFormInputGroup,
} from '@gitlab/ui';
import { mapActions, mapGetters, mapState } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ErrorTrackingForm from './error_tracking_form.vue'; import ErrorTrackingForm from './error_tracking_form.vue';
import ProjectDropdown from './project_dropdown.vue'; import ProjectDropdown from './project_dropdown.vue';
...@@ -12,7 +20,9 @@ export default { ...@@ -12,7 +20,9 @@ export default {
GlFormGroup, GlFormGroup,
GlFormRadioGroup, GlFormRadioGroup,
GlFormRadio, GlFormRadio,
GlFormInputGroup,
ProjectDropdown, ProjectDropdown,
ClipboardButton,
}, },
props: { props: {
initialApiHost: { initialApiHost: {
...@@ -46,6 +56,11 @@ export default { ...@@ -46,6 +56,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
gitlabDsn: {
type: String,
required: false,
default: null,
},
}, },
computed: { computed: {
...mapGetters([ ...mapGetters([
...@@ -63,6 +78,9 @@ export default { ...@@ -63,6 +78,9 @@ export default {
'settingsLoading', 'settingsLoading',
'token', 'token',
]), ]),
showGitlabDsnSetting() {
return this.integrated && this.enabled && this.gitlabDsn;
},
}, },
created() { created() {
this.setInitialState({ this.setInitialState({
...@@ -119,6 +137,17 @@ export default { ...@@ -119,6 +137,17 @@ export default {
</gl-form-radio> </gl-form-radio>
</gl-form-radio-group> </gl-form-radio-group>
</gl-form-group> </gl-form-group>
<gl-form-group
v-if="showGitlabDsnSetting"
:label="__('Paste this DSN into your Sentry SDK')"
data-testid="gitlab-dsn-setting-form"
>
<gl-form-input-group readonly :value="gitlabDsn">
<template #append>
<clipboard-button :text="gitlabDsn" :title="__('Copy')" />
</template>
</gl-form-input-group>
</gl-form-group>
<div v-if="!integrated" class="js-sentry-setting-form" data-testid="sentry-setting-form"> <div v-if="!integrated" class="js-sentry-setting-form" data-testid="sentry-setting-form">
<error-tracking-form /> <error-tracking-form />
<div class="form-group"> <div class="form-group">
......
...@@ -13,6 +13,7 @@ export default () => { ...@@ -13,6 +13,7 @@ export default () => {
token, token,
listProjectsEndpoint, listProjectsEndpoint,
operationsSettingsEndpoint, operationsSettingsEndpoint,
gitlabDsn,
}, },
} = formContainerEl; } = formContainerEl;
...@@ -29,6 +30,7 @@ export default () => { ...@@ -29,6 +30,7 @@ export default () => {
initialToken: token, initialToken: token,
listProjectsEndpoint, listProjectsEndpoint,
operationsSettingsEndpoint, operationsSettingsEndpoint,
gitlabDsn,
}, },
}); });
}, },
......
...@@ -46,6 +46,11 @@ module ErrorTracking ...@@ -46,6 +46,11 @@ module ErrorTracking
after_save :clear_reactive_cache! after_save :clear_reactive_cache!
# When a user enables the integrated error tracking
# we want to immediately provide them with a first
# working client key so they have a DSN for Sentry SDK.
after_save :create_client_key!
def sentry_enabled def sentry_enabled
enabled && !integrated_client? enabled && !integrated_client?
end end
...@@ -54,6 +59,12 @@ module ErrorTracking ...@@ -54,6 +59,12 @@ module ErrorTracking
integrated integrated
end end
def gitlab_dsn
strong_memoize(:gitlab_dsn) do
client_key&.sentry_dsn
end
end
def api_url=(value) def api_url=(value)
super super
clear_memoization(:api_url_slugs) clear_memoization(:api_url_slugs)
...@@ -236,5 +247,19 @@ module ErrorTracking ...@@ -236,5 +247,19 @@ module ErrorTracking
errors.add(:project, 'is a required field') errors.add(:project, 'is a required field')
end end
end end
def client_key
# Project can have multiple client keys.
# However for UI simplicity we render the first active one for user.
# In future we should make it possible to manage client keys from UI.
# Issue https://gitlab.com/gitlab-org/gitlab/-/issues/329596
project.error_tracking_client_keys.active.first
end
def create_client_key!
if enabled? && integrated_client? && !client_key
project.error_tracking_client_keys.create!
end
end
end end
end end
...@@ -18,4 +18,5 @@ ...@@ -18,4 +18,5 @@
api_host: setting.api_host, api_host: setting.api_host,
enabled: setting.enabled.to_json, enabled: setting.enabled.to_json,
integrated: setting.integrated.to_json, integrated: setting.integrated.to_json,
gitlab_dsn: setting.gitlab_dsn,
token: setting.token.present? ? '*' * 12 : nil } } token: setting.token.present? ? '*' * 12 : nil } }
...@@ -24442,6 +24442,9 @@ msgstr "" ...@@ -24442,6 +24442,9 @@ msgstr ""
msgid "Paste project path (i.e. gitlab-org/gitlab)" msgid "Paste project path (i.e. gitlab-org/gitlab)"
msgstr "" msgstr ""
msgid "Paste this DSN into your Sentry SDK"
msgstr ""
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity." msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity."
msgstr "" msgstr ""
......
...@@ -175,6 +175,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do ...@@ -175,6 +175,12 @@ RSpec.describe 'Projects > Settings > For a forked project', :js do
wait_for_requests wait_for_requests
assert_text('Your changes have been saved') assert_text('Your changes have been saved')
within '.js-error-tracking-settings' do
click_button('Expand')
end
expect(page).to have_content('Paste this DSN into your Sentry SDK')
end end
end end
end end
......
import { GlFormRadioGroup, GlFormRadio } from '@gitlab/ui'; import { GlFormRadioGroup, GlFormRadio, GlFormInputGroup } from '@gitlab/ui';
import { createLocalVue, shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { TEST_HOST } from 'helpers/test_constants'; import { TEST_HOST } from 'helpers/test_constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue'; import ErrorTrackingSettings from '~/error_tracking_settings/components/app.vue';
...@@ -12,6 +13,8 @@ import createStore from '~/error_tracking_settings/store'; ...@@ -12,6 +13,8 @@ import createStore from '~/error_tracking_settings/store';
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
const TEST_GITLAB_DSN = 'https://gitlab.example.com/123456';
describe('error tracking settings app', () => { describe('error tracking settings app', () => {
let store; let store;
let wrapper; let wrapper;
...@@ -29,6 +32,10 @@ describe('error tracking settings app', () => { ...@@ -29,6 +32,10 @@ describe('error tracking settings app', () => {
initialProject: null, initialProject: null,
listProjectsEndpoint: TEST_HOST, listProjectsEndpoint: TEST_HOST,
operationsSettingsEndpoint: TEST_HOST, operationsSettingsEndpoint: TEST_HOST,
gitlabDsn: TEST_GITLAB_DSN,
},
stubs: {
GlFormInputGroup, // we need this non-shallow to query for a component within a slot
}, },
}), }),
); );
...@@ -41,6 +48,12 @@ describe('error tracking settings app', () => { ...@@ -41,6 +48,12 @@ describe('error tracking settings app', () => {
findBackendSettingsRadioGroup().findAllComponents(GlFormRadio); findBackendSettingsRadioGroup().findAllComponents(GlFormRadio);
const findElementWithText = (wrappers, text) => wrappers.filter((item) => item.text() === text); const findElementWithText = (wrappers, text) => wrappers.filter((item) => item.text() === text);
const findSentrySettings = () => wrapper.findByTestId('sentry-setting-form'); const findSentrySettings = () => wrapper.findByTestId('sentry-setting-form');
const findDsnSettings = () => wrapper.findByTestId('gitlab-dsn-setting-form');
const enableGitLabErrorTracking = async () => {
findBackendSettingsRadioGroup().vm.$emit('change', true);
await nextTick();
};
beforeEach(() => { beforeEach(() => {
store = createStore(); store = createStore();
...@@ -93,17 +106,35 @@ describe('error tracking settings app', () => { ...@@ -93,17 +106,35 @@ describe('error tracking settings app', () => {
expect(findElementWithText(findBackendSettingsRadioButtons(), 'GitLab')).toHaveLength(1); expect(findElementWithText(findBackendSettingsRadioButtons(), 'GitLab')).toHaveLength(1);
}); });
it('toggles the sentry-settings section when sentry is selected as a tracking-backend', async () => { it('hides the Sentry settings when GitLab is selected as a tracking-backend', async () => {
expect(findSentrySettings().exists()).toBe(true); expect(findSentrySettings().exists()).toBe(true);
// set the "integrated" setting to "true" await enableGitLabErrorTracking();
findBackendSettingsRadioGroup().vm.$emit('change', true);
await nextTick();
expect(findSentrySettings().exists()).toBe(false); expect(findSentrySettings().exists()).toBe(false);
}); });
describe('GitLab DSN section', () => {
it('is visible when GitLab is selected as a tracking-backend and DSN is present', async () => {
expect(findDsnSettings().exists()).toBe(false);
await enableGitLabErrorTracking();
expect(findDsnSettings().exists()).toBe(true);
});
it('contains copy-to-clipboard functionality for the GitLab DSN string', async () => {
await enableGitLabErrorTracking();
const clipBoardInput = findDsnSettings().findComponent(GlFormInputGroup);
const clipBoardButton = findDsnSettings().findComponent(ClipboardButton);
expect(clipBoardInput.props('value')).toBe(TEST_GITLAB_DSN);
expect(clipBoardInput.attributes('readonly')).toBeTruthy();
expect(clipBoardButton.props('text')).toBe(TEST_GITLAB_DSN);
});
});
it.each([true, false])( it.each([true, false])(
'calls the `updateIntegrated` action when the setting changes to `%s`', 'calls the `updateIntegrated` action when the setting changes to `%s`',
(integrated) => { (integrated) => {
......
...@@ -79,6 +79,46 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -79,6 +79,46 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
end end
describe 'Callbacks' do
describe 'after_save :create_client_key!' do
subject { build(:project_error_tracking_setting, :integrated, project: project) }
context 'no client key yet' do
it 'creates a new client key' do
expect { subject.save! }.to change { ErrorTracking::ClientKey.count }.by(1)
end
context 'sentry backend' do
before do
subject.integrated = false
end
it 'does not create a new client key' do
expect { subject.save! }.not_to change { ErrorTracking::ClientKey.count }
end
end
context 'feature disabled' do
before do
subject.enabled = false
end
it 'does not create a new client key' do
expect { subject.save! }.not_to change { ErrorTracking::ClientKey.count }
end
end
end
context 'client key already exists' do
let!(:client_key) { create(:error_tracking_client_key, project: project) }
it 'does not create a new client key' do
expect { subject.save! }.not_to change { ErrorTracking::ClientKey.count }
end
end
end
end
describe '.extract_sentry_external_url' do describe '.extract_sentry_external_url' do
subject { described_class.extract_sentry_external_url(sentry_url) } subject { described_class.extract_sentry_external_url(sentry_url) }
...@@ -494,4 +534,10 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -494,4 +534,10 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
it { expect(subject.sentry_enabled).to eq(sentry_enabled) } it { expect(subject.sentry_enabled).to eq(sentry_enabled) }
end end
end end
describe '#gitlab_dsn' do
let!(:client_key) { create(:error_tracking_client_key, project: project) }
it { expect(subject.gitlab_dsn).to eq(client_key.sentry_dsn) }
end
end end
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