Commit 99ebbe65 authored by Sean Arnold's avatar Sean Arnold Committed by Peter Hegman

Disallow integrated error tracking by default

Introduce feature flag. Hide UI and prevent API calls

Changelog: changed
parent e0f5dc24
...@@ -26,7 +26,7 @@ import { ...@@ -26,7 +26,7 @@ import {
trackErrorStatusUpdateOptions, trackErrorStatusUpdateOptions,
} from '../utils'; } from '../utils';
import { severityLevel, severityLevelVariant, errorStatus } from './constants'; import { severityLevel, severityLevelVariant, errorStatus } from '../constants';
import Stacktrace from './stacktrace.vue'; import Stacktrace from './stacktrace.vue';
const SENTRY_TIMEOUT = 10000; const SENTRY_TIMEOUT = 10000;
......
<script> <script>
import { import {
GlAlert,
GlEmptyState, GlEmptyState,
GlButton, GlButton,
GlIcon, GlIcon,
...@@ -10,6 +11,7 @@ import { ...@@ -10,6 +11,7 @@ import {
GlDropdown, GlDropdown,
GlDropdownItem, GlDropdownItem,
GlDropdownDivider, GlDropdownDivider,
GlSprintf,
GlTooltipDirective, GlTooltipDirective,
GlPagination, GlPagination,
} from '@gitlab/ui'; } from '@gitlab/ui';
...@@ -21,6 +23,7 @@ import { __ } from '~/locale'; ...@@ -21,6 +23,7 @@ import { __ } from '~/locale';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils'; import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils';
import { I18N_ERROR_TRACKING_LIST } from '../constants';
import ErrorTrackingActions from './error_tracking_actions.vue'; import ErrorTrackingActions from './error_tracking_actions.vue';
export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center'; export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
...@@ -29,6 +32,7 @@ export default { ...@@ -29,6 +32,7 @@ export default {
FIRST_PAGE: 1, FIRST_PAGE: 1,
PREV_PAGE: 1, PREV_PAGE: 1,
NEXT_PAGE: 2, NEXT_PAGE: 2,
i18n: I18N_ERROR_TRACKING_LIST,
fields: [ fields: [
{ {
key: 'error', key: 'error',
...@@ -71,6 +75,7 @@ export default { ...@@ -71,6 +75,7 @@ export default {
frequency: __('Frequency'), frequency: __('Frequency'),
}, },
components: { components: {
GlAlert,
GlEmptyState, GlEmptyState,
GlButton, GlButton,
GlDropdown, GlDropdown,
...@@ -81,6 +86,7 @@ export default { ...@@ -81,6 +86,7 @@ export default {
GlLoadingIcon, GlLoadingIcon,
GlTable, GlTable,
GlFormInput, GlFormInput,
GlSprintf,
GlPagination, GlPagination,
TimeAgo, TimeAgo,
ErrorTrackingActions, ErrorTrackingActions,
...@@ -117,12 +123,17 @@ export default { ...@@ -117,12 +123,17 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
showIntegratedTrackingDisabledAlert: {
type: Boolean,
required: false,
},
}, },
hasLocalStorage: AccessorUtils.canUseLocalStorage(), hasLocalStorage: AccessorUtils.canUseLocalStorage(),
data() { data() {
return { return {
errorSearchQuery: '', errorSearchQuery: '',
pageValue: this.$options.FIRST_PAGE, pageValue: this.$options.FIRST_PAGE,
isAlertDismissed: false,
}; };
}, },
computed: { computed: {
...@@ -142,6 +153,9 @@ export default { ...@@ -142,6 +153,9 @@ export default {
errorTrackingHelpUrl() { errorTrackingHelpUrl() {
return helpPagePath('operations/error_tracking'); return helpPagePath('operations/error_tracking');
}, },
showIntegratedDisabledAlert() {
return !this.isAlertDismissed && this.showIntegratedTrackingDisabledAlert;
},
}, },
watch: { watch: {
pagination() { pagination() {
...@@ -150,6 +164,8 @@ export default { ...@@ -150,6 +164,8 @@ export default {
} }
}, },
}, },
epicLink: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353639',
featureFlagLink: helpPagePath('operations/error_tracking'),
created() { created() {
if (this.errorTrackingEnabled) { if (this.errorTrackingEnabled) {
this.setEndpoint(this.indexPath); this.setEndpoint(this.indexPath);
...@@ -232,6 +248,34 @@ export default { ...@@ -232,6 +248,34 @@ export default {
<template> <template>
<div class="error-list"> <div class="error-list">
<div v-if="errorTrackingEnabled"> <div v-if="errorTrackingEnabled">
<gl-alert
v-if="showIntegratedDisabledAlert"
variant="danger"
data-testid="integrated-disabled-alert"
@dismiss="isAlertDismissed = true"
>
<gl-sprintf :message="this.$options.i18n.integratedErrorTrackingDisabledText">
<template #epicLink="{ content }">
<gl-link :href="$options.epicLink" target="_blank">{{ content }}</gl-link>
</template>
<template #flagLink="{ content }">
<gl-link :href="$options.featureFlagLink" target="_blank">{{ content }}</gl-link>
</template>
<template #settingsLink="{ content }">
<gl-link :href="enableErrorTrackingLink" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
<div>
<gl-button
category="primary"
variant="confirm"
:href="enableErrorTrackingLink"
class="gl-mr-auto gl-mt-3"
>
{{ $options.i18n.viewProjectSettingsButton }}
</gl-button>
</div>
</gl-alert>
<div <div
class="row flex-column flex-md-row align-items-md-center m-0 mt-sm-2 p-3 p-sm-3 bg-secondary border" class="row flex-column flex-md-row align-items-md-center m-0 mt-sm-2 p-3 p-sm-3 bg-secondary border"
> >
......
import { s__ } from '~/locale';
export const severityLevel = { export const severityLevel = {
FATAL: 'fatal', FATAL: 'fatal',
ERROR: 'error', ERROR: 'error',
...@@ -19,3 +21,10 @@ export const errorStatus = { ...@@ -19,3 +21,10 @@ export const errorStatus = {
RESOLVED: 'resolved', RESOLVED: 'resolved',
UNRESOLVED: 'unresolved', UNRESOLVED: 'unresolved',
}; };
export const I18N_ERROR_TRACKING_LIST = {
integratedErrorTrackingDisabledText: s__(
'ErrorTracking|Integrated error tracking is %{epicLinkStart}turned off by default%{epicLinkEnd} and no longer active for this project. To re-enable error tracking on self-hosted instances, you can either %{flagLinkStart}turn on the feature flag%{flagLinkEnd} for integrated error tracking, or provide a %{settingsLinkStart}Sentry API URL and Auth Token%{settingsLinkEnd} on your project settings page. However, error tracking is not ready for production use and cannot be enabled on GitLab.com.',
),
viewProjectSettingsButton: s__('ErrorTracking|View project settings'),
};
...@@ -14,10 +14,15 @@ export default () => { ...@@ -14,10 +14,15 @@ export default () => {
projectPath, projectPath,
listPath, listPath,
} = domEl.dataset; } = domEl.dataset;
let { errorTrackingEnabled, userCanEnableErrorTracking } = domEl.dataset; let {
errorTrackingEnabled,
userCanEnableErrorTracking,
showIntegratedTrackingDisabledAlert,
} = domEl.dataset;
errorTrackingEnabled = parseBoolean(errorTrackingEnabled); errorTrackingEnabled = parseBoolean(errorTrackingEnabled);
userCanEnableErrorTracking = parseBoolean(userCanEnableErrorTracking); userCanEnableErrorTracking = parseBoolean(userCanEnableErrorTracking);
showIntegratedTrackingDisabledAlert = parseBoolean(showIntegratedTrackingDisabledAlert);
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -36,6 +41,7 @@ export default () => { ...@@ -36,6 +41,7 @@ export default () => {
userCanEnableErrorTracking, userCanEnableErrorTracking,
projectPath, projectPath,
listPath, listPath,
showIntegratedTrackingDisabledAlert,
}, },
}); });
}, },
......
<script> <script>
import { import {
GlAlert,
GlButton, GlButton,
GlFormGroup, GlFormGroup,
GlFormCheckbox, GlFormCheckbox,
GlFormRadioGroup, GlFormRadioGroup,
GlFormRadio, GlFormRadio,
GlFormInputGroup, GlFormInputGroup,
GlLink,
GlSprintf,
} from '@gitlab/ui'; } 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 ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { helpPagePath } from '~/helpers/help_page_helper';
import { I18N_ERROR_TRACKING_SETTINGS } from '../constants';
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';
export default { export default {
i18n: I18N_ERROR_TRACKING_SETTINGS,
components: { components: {
ErrorTrackingForm, ErrorTrackingForm,
GlAlert,
GlButton, GlButton,
GlFormCheckbox, GlFormCheckbox,
GlFormGroup, GlFormGroup,
GlFormRadioGroup, GlFormRadioGroup,
GlFormRadio, GlFormRadio,
GlFormInputGroup, GlFormInputGroup,
GlLink,
GlSprintf,
ProjectDropdown, ProjectDropdown,
ClipboardButton, ClipboardButton,
}, },
mixins: [glFeatureFlagsMixin()],
props: { props: {
initialApiHost: { initialApiHost: {
type: String, type: String,
...@@ -62,6 +73,11 @@ export default { ...@@ -62,6 +73,11 @@ export default {
default: null, default: null,
}, },
}, },
data() {
return {
isAlertDismissed: false,
};
},
computed: { computed: {
...mapGetters([ ...mapGetters([
'dropdownLabel', 'dropdownLabel',
...@@ -81,12 +97,34 @@ export default { ...@@ -81,12 +97,34 @@ export default {
showGitlabDsnSetting() { showGitlabDsnSetting() {
return this.integrated && this.enabled && this.gitlabDsn; return this.integrated && this.enabled && this.gitlabDsn;
}, },
showIntegratedErrorTracking() {
return this.glFeatures.integratedErrorTracking === true;
},
setInitialEnabled() {
if (this.showIntegratedErrorTracking) {
return this.initialEnabled;
}
if (this.initialIntegrated === 'true') {
return 'false';
}
return this.initialEnabled;
},
showIntegratedTrackingDisabledAlert() {
return (
!this.isAlertDismissed &&
!this.showIntegratedErrorTracking &&
this.initialIntegrated === 'true' &&
this.initialEnabled === 'true'
);
},
}, },
epicLink: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353639',
featureFlagLink: helpPagePath('operations/error_tracking'),
created() { created() {
this.setInitialState({ this.setInitialState({
apiHost: this.initialApiHost, apiHost: this.initialApiHost,
enabled: this.initialEnabled, enabled: this.setInitialEnabled,
integrated: this.initialIntegrated, integrated: this.showIntegratedErrorTracking && this.initialIntegrated,
project: this.initialProject, project: this.initialProject,
token: this.initialToken, token: this.initialToken,
listProjectsEndpoint: this.listProjectsEndpoint, listProjectsEndpoint: this.listProjectsEndpoint,
...@@ -104,21 +142,41 @@ export default { ...@@ -104,21 +142,41 @@ export default {
handleSubmit() { handleSubmit() {
this.updateSettings(); this.updateSettings();
}, },
dismissAlert() {
this.isAlertDismissed = true;
},
}, },
}; };
</script> </script>
<template> <template>
<div> <div>
<gl-alert v-if="showIntegratedTrackingDisabledAlert" variant="danger" @dismiss="dismissAlert">
<gl-sprintf :message="this.$options.i18n.integratedErrorTrackingDisabledText">
<template #epicLink="{ content }">
<gl-link :href="$options.epicLink" target="_blank">{{ content }}</gl-link>
</template>
<template #flagLink="{ content }">
<gl-link :href="$options.featureFlagLink" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<gl-form-group <gl-form-group
:label="s__('ErrorTracking|Enable error tracking')" :label="s__('ErrorTracking|Enable error tracking')"
label-for="error-tracking-enabled" label-for="error-tracking-enabled"
> >
<gl-form-checkbox id="error-tracking-enabled" :checked="enabled" @change="updateEnabled"> <gl-form-checkbox
id="error-tracking-enabled"
:checked="enabled"
data-testid="error-tracking-enabled"
@change="updateEnabled"
>
{{ s__('ErrorTracking|Active') }} {{ s__('ErrorTracking|Active') }}
</gl-form-checkbox> </gl-form-checkbox>
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
v-if="showIntegratedErrorTracking"
:label="s__('ErrorTracking|Error tracking backend')" :label="s__('ErrorTracking|Error tracking backend')"
data-testid="tracking-backend-settings" data-testid="tracking-backend-settings"
> >
......
import { s__ } from '~/locale';
export const I18N_ERROR_TRACKING_SETTINGS = {
integratedErrorTrackingDisabledText: s__(
'ErrorTracking|Integrated error tracking is %{epicLinkStart}turned off by default%{epicLinkEnd} and no longer active for this project. To re-enable error tracking on self-hosted instances, you can either %{flagLinkStart}turn on the feature flag%{flagLinkEnd} for integrated error tracking, or provide a Sentry API URL and Auth Token below. However, error tracking is not ready for production use and cannot be enabled on GitLab.com.',
),
};
...@@ -6,6 +6,10 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle ...@@ -6,6 +6,10 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
before_action :authorize_read_sentry_issue! before_action :authorize_read_sentry_issue!
before_action :set_issue_id, only: :details before_action :set_issue_id, only: :details
before_action only: [:index] do
push_frontend_feature_flag(:integrated_error_tracking, project)
end
def index def index
respond_to do |format| respond_to do |format|
format.html format.html
......
...@@ -7,6 +7,10 @@ module Projects ...@@ -7,6 +7,10 @@ module Projects
before_action :authorize_admin_operations! before_action :authorize_admin_operations!
before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token] before_action :authorize_read_prometheus_alerts!, only: [:reset_alerting_token]
before_action do
push_frontend_feature_flag(:integrated_error_tracking, project)
end
respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token] respond_to :json, only: [:reset_alerting_token, :reset_pagerduty_token]
helper_method :error_tracking_setting helper_method :error_tracking_setting
......
...@@ -12,7 +12,8 @@ module Projects::ErrorTrackingHelper ...@@ -12,7 +12,8 @@ module Projects::ErrorTrackingHelper
'error-tracking-enabled' => error_tracking_enabled.to_s, 'error-tracking-enabled' => error_tracking_enabled.to_s,
'project-path' => project.full_path, 'project-path' => project.full_path,
'list-path' => project_error_tracking_index_path(project), 'list-path' => project_error_tracking_index_path(project),
'illustration-path' => image_path('illustrations/cluster_popover.svg') 'illustration-path' => image_path('illustrations/cluster_popover.svg'),
'show-integrated-tracking-disabled-alert' => show_integrated_tracking_disabled_alert?(project).to_s
} }
end end
...@@ -27,4 +28,15 @@ module Projects::ErrorTrackingHelper ...@@ -27,4 +28,15 @@ module Projects::ErrorTrackingHelper
'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts) 'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts)
} }
end end
private
def show_integrated_tracking_disabled_alert?(project)
return false if ::Feature.enabled?(:integrated_error_tracking, project)
setting ||= project.error_tracking_setting ||
project.build_error_tracking_setting
setting.integrated_enabled?
end
end end
...@@ -59,6 +59,10 @@ module ErrorTracking ...@@ -59,6 +59,10 @@ module ErrorTracking
integrated integrated
end end
def integrated_enabled?
enabled? && integrated_client?
end
def gitlab_dsn def gitlab_dsn
strong_memoize(:gitlab_dsn) do strong_memoize(:gitlab_dsn) do
client_key&.sentry_dsn client_key&.sentry_dsn
......
---
name: integrated_error_tracking
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81767
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/353956
milestone: '14.9'
type: development
group: group::respond
default_enabled: false
...@@ -129,7 +129,17 @@ If another event occurs, the error reverts to unresolved. ...@@ -129,7 +129,17 @@ If another event occurs, the error reverts to unresolved.
## Integrated error tracking ## Integrated error tracking
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329596) in GitLab 14.4. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/329596) in GitLab 14.4.
> - [Disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/353639) in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `integrated_error_tracking`. Disabled by default.
FLAG:
By default this feature is not available. To make it available on self-managed GitLab, ask an
administrator to [enable the feature flag](../administration/feature_flags.md)
named `integrated_error_tracking`. The feature is not ready for production use.
On GitLab.com, this feature is not available.
WARNING:
Turning on integrated error tracking may impact performance, depending on your error rates.
Integrated error tracking is a lightweight alternative to Sentry backend. Integrated error tracking is a lightweight alternative to Sentry backend.
You still use Sentry SDK with your application. But you don't need to deploy Sentry You still use Sentry SDK with your application. But you don't need to deploy Sentry
......
...@@ -9,6 +9,12 @@ module API ...@@ -9,6 +9,12 @@ module API
expose :sentry_external_url expose :sentry_external_url
expose :api_url expose :api_url
expose :integrated expose :integrated
def integrated
return false unless ::Feature.enabled?(:integrated_error_tracking, object.project)
object.integrated_client?
end
end end
class ClientKey < Grape::Entity class ClientKey < Grape::Entity
......
...@@ -28,8 +28,8 @@ module API ...@@ -28,8 +28,8 @@ module API
end end
def feature_enabled? def feature_enabled?
project.error_tracking_setting&.enabled? && Feature.enabled?(:integrated_error_tracking, project) &&
project.error_tracking_setting&.integrated_client? project.error_tracking_setting&.integrated_enabled?
end end
def find_client_key(public_key) def find_client_key(public_key)
......
...@@ -14443,6 +14443,12 @@ msgstr "" ...@@ -14443,6 +14443,12 @@ msgstr ""
msgid "ErrorTracking|If you self-host Sentry, enter your Sentry instance's full URL. If you use Sentry's hosted solution, enter https://sentry.io" msgid "ErrorTracking|If you self-host Sentry, enter your Sentry instance's full URL. If you use Sentry's hosted solution, enter https://sentry.io"
msgstr "" msgstr ""
msgid "ErrorTracking|Integrated error tracking is %{epicLinkStart}turned off by default%{epicLinkEnd} and no longer active for this project. To re-enable error tracking on self-hosted instances, you can either %{flagLinkStart}turn on the feature flag%{flagLinkEnd} for integrated error tracking, or provide a %{settingsLinkStart}Sentry API URL and Auth Token%{settingsLinkEnd} on your project settings page. However, error tracking is not ready for production use and cannot be enabled on GitLab.com."
msgstr ""
msgid "ErrorTracking|Integrated error tracking is %{epicLinkStart}turned off by default%{epicLinkEnd} and no longer active for this project. To re-enable error tracking on self-hosted instances, you can either %{flagLinkStart}turn on the feature flag%{flagLinkEnd} for integrated error tracking, or provide a Sentry API URL and Auth Token below. However, error tracking is not ready for production use and cannot be enabled on GitLab.com."
msgstr ""
msgid "ErrorTracking|No projects available" msgid "ErrorTracking|No projects available"
msgstr "" msgstr ""
...@@ -14452,6 +14458,9 @@ msgstr "" ...@@ -14452,6 +14458,9 @@ msgstr ""
msgid "ErrorTracking|To enable project selection, enter a valid Auth Token." msgid "ErrorTracking|To enable project selection, enter a valid Auth Token."
msgstr "" msgstr ""
msgid "ErrorTracking|View project settings"
msgstr ""
msgid "Errors" msgid "Errors"
msgstr "" msgstr ""
......
...@@ -10,11 +10,7 @@ import { ...@@ -10,11 +10,7 @@ import {
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue'; import Vue, { nextTick } from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import { import { severityLevel, severityLevelVariant, errorStatus } from '~/error_tracking/constants';
severityLevel,
severityLevelVariant,
errorStatus,
} from '~/error_tracking/components/constants';
import ErrorDetails from '~/error_tracking/components/error_details.vue'; import ErrorDetails from '~/error_tracking/components/error_details.vue';
import Stacktrace from '~/error_tracking/components/stacktrace.vue'; import Stacktrace from '~/error_tracking/components/stacktrace.vue';
import { import {
......
...@@ -7,6 +7,7 @@ import ErrorTrackingActions from '~/error_tracking/components/error_tracking_act ...@@ -7,6 +7,7 @@ import ErrorTrackingActions from '~/error_tracking/components/error_tracking_act
import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue'; import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils'; import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import errorsList from './list_mock.json'; import errorsList from './list_mock.json';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -25,28 +26,33 @@ describe('ErrorTrackingList', () => { ...@@ -25,28 +26,33 @@ describe('ErrorTrackingList', () => {
const findLoadingIcon = () => wrapper.find(GlLoadingIcon); const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findPagination = () => wrapper.find(GlPagination); const findPagination = () => wrapper.find(GlPagination);
const findErrorActions = () => wrapper.find(ErrorTrackingActions); const findErrorActions = () => wrapper.find(ErrorTrackingActions);
const findIntegratedDisabledAlert = () => wrapper.findByTestId('integrated-disabled-alert');
function mountComponent({ function mountComponent({
errorTrackingEnabled = true, errorTrackingEnabled = true,
userCanEnableErrorTracking = true, userCanEnableErrorTracking = true,
showIntegratedTrackingDisabledAlert = false,
stubs = {}, stubs = {},
} = {}) { } = {}) {
wrapper = mount(ErrorTrackingList, { wrapper = extendedWrapper(
store, mount(ErrorTrackingList, {
propsData: { store,
indexPath: '/path', propsData: {
listPath: '/error_tracking', indexPath: '/path',
projectPath: 'project/test', listPath: '/error_tracking',
enableErrorTrackingLink: '/link', projectPath: 'project/test',
userCanEnableErrorTracking, enableErrorTrackingLink: '/link',
errorTrackingEnabled, userCanEnableErrorTracking,
illustrationPath: 'illustration/path', errorTrackingEnabled,
}, showIntegratedTrackingDisabledAlert,
stubs: { illustrationPath: 'illustration/path',
...stubChildren(ErrorTrackingList), },
...stubs, stubs: {
}, ...stubChildren(ErrorTrackingList),
}); ...stubs,
},
}),
);
} }
beforeEach(() => { beforeEach(() => {
...@@ -223,6 +229,31 @@ describe('ErrorTrackingList', () => { ...@@ -223,6 +229,31 @@ describe('ErrorTrackingList', () => {
}); });
}); });
describe('When the integrated tracking diabled alert should be shown', () => {
beforeEach(() => {
mountComponent({
showIntegratedTrackingDisabledAlert: true,
stubs: {
GlAlert: false,
},
});
});
it('shows the alert box', () => {
expect(findIntegratedDisabledAlert().exists()).toBe(true);
});
describe('when alert is dismissed', () => {
it('hides the alert box', async () => {
findIntegratedDisabledAlert().vm.$emit('dismiss');
await nextTick();
expect(findIntegratedDisabledAlert().exists()).toBe(false);
});
});
});
describe('When the ignore button on an error is clicked', () => { describe('When the ignore button on an error is clicked', () => {
beforeEach(() => { beforeEach(() => {
store.state.list.loading = false; store.state.list.loading = false;
......
...@@ -18,19 +18,27 @@ describe('error tracking settings app', () => { ...@@ -18,19 +18,27 @@ describe('error tracking settings app', () => {
let store; let store;
let wrapper; let wrapper;
function mountComponent() { const defaultProps = {
initialEnabled: 'true',
initialIntegrated: 'false',
initialApiHost: TEST_HOST,
initialToken: 'someToken',
initialProject: null,
listProjectsEndpoint: TEST_HOST,
operationsSettingsEndpoint: TEST_HOST,
gitlabDsn: TEST_GITLAB_DSN,
};
function mountComponent({
glFeatures = { integratedErrorTracking: false },
props = defaultProps,
} = {}) {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(ErrorTrackingSettings, { shallowMount(ErrorTrackingSettings, {
store, // Override the imported store store, // Override the imported store
propsData: { propsData: { ...props },
initialEnabled: 'true', provide: {
initialIntegrated: 'false', glFeatures,
initialApiHost: TEST_HOST,
initialToken: 'someToken',
initialProject: null,
listProjectsEndpoint: TEST_HOST,
operationsSettingsEndpoint: TEST_HOST,
gitlabDsn: TEST_GITLAB_DSN,
}, },
stubs: { stubs: {
GlFormInputGroup, // we need this non-shallow to query for a component within a slot GlFormInputGroup, // we need this non-shallow to query for a component within a slot
...@@ -47,6 +55,7 @@ describe('error tracking settings app', () => { ...@@ -47,6 +55,7 @@ describe('error tracking settings app', () => {
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 findDsnSettings = () => wrapper.findByTestId('gitlab-dsn-setting-form');
const findEnabledCheckbox = () => wrapper.findByTestId('error-tracking-enabled');
const enableGitLabErrorTracking = async () => { const enableGitLabErrorTracking = async () => {
findBackendSettingsRadioGroup().vm.$emit('change', true); findBackendSettingsRadioGroup().vm.$emit('change', true);
...@@ -88,62 +97,104 @@ describe('error tracking settings app', () => { ...@@ -88,62 +97,104 @@ describe('error tracking settings app', () => {
}); });
describe('tracking-backend settings', () => { describe('tracking-backend settings', () => {
it('contains a form-group with the correct label', () => { it('does not contain backend settings section', () => {
expect(findBackendSettingsSection().attributes('label')).toBe('Error tracking backend'); expect(findBackendSettingsSection().exists()).toBe(false);
}); });
it('contains a radio group', () => { it('shows the sentry form', () => {
expect(findBackendSettingsRadioGroup().exists()).toBe(true); expect(findSentrySettings().exists()).toBe(true);
}); });
it('contains the correct radio buttons', () => { describe('enabled setting is true', () => {
expect(findBackendSettingsRadioButtons()).toHaveLength(2); describe('integrated setting is true', () => {
beforeEach(() => {
mountComponent({
props: { ...defaultProps, initialEnabled: 'true', initialIntegrated: 'true' },
});
});
it('displays enabled as false', () => {
expect(findEnabledCheckbox().attributes('checked')).toBeUndefined();
});
});
describe('integrated setting is false', () => {
beforeEach(() => {
mountComponent({
props: { ...defaultProps, initialEnabled: 'true', initialIntegrated: 'false' },
});
});
expect(findElementWithText(findBackendSettingsRadioButtons(), 'Sentry')).toHaveLength(1); it('displays enabled as true', () => {
expect(findElementWithText(findBackendSettingsRadioButtons(), 'GitLab')).toHaveLength(1); expect(findEnabledCheckbox().attributes('checked')).toBe('true');
});
});
}); });
it('hides the Sentry settings when GitLab is selected as a tracking-backend', async () => { describe('integrated_error_tracking feature flag enabled', () => {
expect(findSentrySettings().exists()).toBe(true); beforeEach(() => {
mountComponent({
glFeatures: { integratedErrorTracking: true },
});
});
await enableGitLabErrorTracking(); it('contains a form-group with the correct label', () => {
expect(findBackendSettingsSection().attributes('label')).toBe('Error tracking backend');
});
expect(findSentrySettings().exists()).toBe(false); it('contains a radio group', () => {
}); expect(findBackendSettingsRadioGroup().exists()).toBe(true);
});
describe('GitLab DSN section', () => { it('contains the correct radio buttons', () => {
it('is visible when GitLab is selected as a tracking-backend and DSN is present', async () => { expect(findBackendSettingsRadioButtons()).toHaveLength(2);
expect(findDsnSettings().exists()).toBe(false);
expect(findElementWithText(findBackendSettingsRadioButtons(), 'Sentry')).toHaveLength(1);
expect(findElementWithText(findBackendSettingsRadioButtons(), 'GitLab')).toHaveLength(1);
});
it('hides the Sentry settings when GitLab is selected as a tracking-backend', async () => {
expect(findSentrySettings().exists()).toBe(true);
await enableGitLabErrorTracking(); await enableGitLabErrorTracking();
expect(findDsnSettings().exists()).toBe(true); expect(findSentrySettings().exists()).toBe(false);
}); });
it('contains copy-to-clipboard functionality for the GitLab DSN string', async () => { describe('GitLab DSN section', () => {
await enableGitLabErrorTracking(); 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);
});
const clipBoardInput = findDsnSettings().findComponent(GlFormInputGroup); it('contains copy-to-clipboard functionality for the GitLab DSN string', async () => {
const clipBoardButton = findDsnSettings().findComponent(ClipboardButton); await enableGitLabErrorTracking();
expect(clipBoardInput.props('value')).toBe(TEST_GITLAB_DSN); const clipBoardInput = findDsnSettings().findComponent(GlFormInputGroup);
expect(clipBoardInput.attributes('readonly')).toBeTruthy(); const clipBoardButton = findDsnSettings().findComponent(ClipboardButton);
expect(clipBoardButton.props('text')).toBe(TEST_GITLAB_DSN);
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) => {
jest.spyOn(store, 'dispatch').mockImplementation(); jest.spyOn(store, 'dispatch').mockImplementation();
expect(store.dispatch).toHaveBeenCalledTimes(0); expect(store.dispatch).toHaveBeenCalledTimes(0);
findBackendSettingsRadioGroup().vm.$emit('change', integrated); findBackendSettingsRadioGroup().vm.$emit('change', integrated);
expect(store.dispatch).toHaveBeenCalledTimes(1); expect(store.dispatch).toHaveBeenCalledTimes(1);
expect(store.dispatch).toHaveBeenCalledWith('updateIntegrated', integrated); expect(store.dispatch).toHaveBeenCalledWith('updateIntegrated', integrated);
}, },
); );
});
}); });
}); });
...@@ -34,7 +34,8 @@ RSpec.describe Projects::ErrorTrackingHelper do ...@@ -34,7 +34,8 @@ RSpec.describe Projects::ErrorTrackingHelper do
'error-tracking-enabled' => 'false', 'error-tracking-enabled' => 'false',
'list-path' => list_path, 'list-path' => list_path,
'project-path' => project_path, 'project-path' => project_path,
'illustration-path' => match_asset_path('/assets/illustrations/cluster_popover.svg') 'illustration-path' => match_asset_path('/assets/illustrations/cluster_popover.svg'),
'show-integrated-tracking-disabled-alert' => 'false'
) )
end end
end end
...@@ -67,6 +68,37 @@ RSpec.describe Projects::ErrorTrackingHelper do ...@@ -67,6 +68,37 @@ RSpec.describe Projects::ErrorTrackingHelper do
) )
end end
end end
context 'with integrated error tracking feature' do
using RSpec::Parameterized::TableSyntax
where(:feature_flag, :enabled, :integrated, :show_alert) do
false | true | true | true
false | true | false | false
false | false | true | false
false | false | false | false
true | true | true | false
true | true | false | false
true | false | true | false
true | false | false | false
end
with_them do
before do
stub_feature_flags(integrated_error_tracking: feature_flag)
error_tracking_setting.update_columns(
enabled: enabled,
integrated: integrated
)
end
specify do
expect(helper.error_tracking_data(current_user, project)).to include(
'show-integrated-tracking-disabled-alert' => show_alert.to_s
)
end
end
end
end end
context 'when user is not maintainer' do context 'when user is not maintainer' do
......
...@@ -535,6 +535,25 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do ...@@ -535,6 +535,25 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
end end
describe '#integrated_enabled?' do
using RSpec::Parameterized::TableSyntax
where(:enabled, :integrated, :integrated_enabled) do
true | false | false
false | true | false
true | true | true
end
with_them do
before do
subject.enabled = enabled
subject.integrated = integrated
end
it { expect(subject.integrated_enabled?).to eq(integrated_enabled) }
end
end
describe '#gitlab_dsn' do describe '#gitlab_dsn' do
let!(:client_key) { create(:error_tracking_client_key, project: project) } let!(:client_key) { create(:error_tracking_client_key, project: project) }
......
...@@ -26,7 +26,6 @@ RSpec.describe API::ErrorTracking::Collector do ...@@ -26,7 +26,6 @@ RSpec.describe API::ErrorTracking::Collector do
RSpec.shared_examples 'successful request' do RSpec.shared_examples 'successful request' do
it 'writes to the database and returns OK', :aggregate_failures do it 'writes to the database and returns OK', :aggregate_failures do
expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1) expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end end
end end
...@@ -42,6 +41,14 @@ RSpec.describe API::ErrorTracking::Collector do ...@@ -42,6 +41,14 @@ RSpec.describe API::ErrorTracking::Collector do
it_behaves_like 'successful request' it_behaves_like 'successful request'
context 'intergrated error tracking feature flag is disabled' do
before do
stub_feature_flags(integrated_error_tracking: false)
end
it_behaves_like 'not found'
end
context 'error tracking feature is disabled' do context 'error tracking feature is disabled' do
before do before do
setting.update!(enabled: false) setting.update!(enabled: false)
......
...@@ -23,6 +23,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do ...@@ -23,6 +23,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end end
end end
shared_examples 'returns project settings with false for integrated' do
specify do
make_request
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(
'active' => setting.reload.enabled,
'project_name' => setting.project_name,
'sentry_external_url' => setting.sentry_external_url,
'api_url' => setting.api_url,
'integrated' => false
)
end
end
shared_examples 'returns 404' do shared_examples 'returns 404' do
it 'returns no project settings' do it 'returns no project settings' do
make_request make_request
...@@ -46,7 +61,17 @@ RSpec.describe API::ErrorTracking::ProjectSettings do ...@@ -46,7 +61,17 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end end
context 'patch settings' do context 'patch settings' do
it_behaves_like 'returns project settings' context 'integrated_error_tracking feature enabled' do
it_behaves_like 'returns project settings'
end
context 'integrated_error_tracking feature disabled' do
before do
stub_feature_flags(integrated_error_tracking: false)
end
it_behaves_like 'returns project settings with false for integrated'
end
it 'updates enabled flag' do it 'updates enabled flag' do
expect(setting).to be_enabled expect(setting).to be_enabled
...@@ -84,13 +109,19 @@ RSpec.describe API::ErrorTracking::ProjectSettings do ...@@ -84,13 +109,19 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
context 'with integrated param' do context 'with integrated param' do
let(:params) { { active: true, integrated: true } } let(:params) { { active: true, integrated: true } }
it 'updates the integrated flag' do context 'integrated_error_tracking feature enabled' do
expect(setting.integrated).to be_falsey before do
stub_feature_flags(integrated_error_tracking: true)
end
make_request it 'updates the integrated flag' do
expect(setting.integrated).to be_falsey
make_request
expect(json_response).to include('integrated' => true) expect(json_response).to include('integrated' => true)
expect(setting.reload.integrated).to be_truthy expect(setting.reload.integrated).to be_truthy
end
end end
end end
end end
...@@ -170,7 +201,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do ...@@ -170,7 +201,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
end end
context 'get settings' do context 'get settings' do
it_behaves_like 'returns project settings' context 'integrated_error_tracking feature enabled' do
before do
stub_feature_flags(integrated_error_tracking: true)
end
it_behaves_like 'returns project settings'
end
context 'integrated_error_tracking feature disabled' do
before do
stub_feature_flags(integrated_error_tracking: false)
end
it_behaves_like 'returns project settings with false for integrated'
end
end 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