Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1bcebb67d5
commit
237ead18b9
|
@ -1,21 +0,0 @@
|
|||
export const severityLevel = {
|
||||
FATAL: 'fatal',
|
||||
ERROR: 'error',
|
||||
WARNING: 'warning',
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
};
|
||||
|
||||
export const severityLevelVariant = {
|
||||
[severityLevel.FATAL]: 'danger',
|
||||
[severityLevel.ERROR]: 'neutral',
|
||||
[severityLevel.WARNING]: 'warning',
|
||||
[severityLevel.INFO]: 'info',
|
||||
[severityLevel.DEBUG]: 'muted',
|
||||
};
|
||||
|
||||
export const errorStatus = {
|
||||
IGNORED: 'ignored',
|
||||
RESOLVED: 'resolved',
|
||||
UNRESOLVED: 'unresolved',
|
||||
};
|
|
@ -26,7 +26,7 @@ import {
|
|||
trackErrorStatusUpdateOptions,
|
||||
} from '../utils';
|
||||
|
||||
import { severityLevel, severityLevelVariant, errorStatus } from './constants';
|
||||
import { severityLevel, severityLevelVariant, errorStatus } from '../constants';
|
||||
import Stacktrace from './stacktrace.vue';
|
||||
|
||||
const SENTRY_TIMEOUT = 10000;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import {
|
||||
GlAlert,
|
||||
GlEmptyState,
|
||||
GlButton,
|
||||
GlIcon,
|
||||
|
@ -10,6 +11,7 @@ import {
|
|||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownDivider,
|
||||
GlSprintf,
|
||||
GlTooltipDirective,
|
||||
GlPagination,
|
||||
} from '@gitlab/ui';
|
||||
|
@ -21,6 +23,7 @@ import { __ } from '~/locale';
|
|||
import Tracking from '~/tracking';
|
||||
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '../utils';
|
||||
import { I18N_ERROR_TRACKING_LIST } from '../constants';
|
||||
import ErrorTrackingActions from './error_tracking_actions.vue';
|
||||
|
||||
export const tableDataClass = 'table-col d-flex d-md-table-cell align-items-center';
|
||||
|
@ -29,6 +32,7 @@ export default {
|
|||
FIRST_PAGE: 1,
|
||||
PREV_PAGE: 1,
|
||||
NEXT_PAGE: 2,
|
||||
i18n: I18N_ERROR_TRACKING_LIST,
|
||||
fields: [
|
||||
{
|
||||
key: 'error',
|
||||
|
@ -71,6 +75,7 @@ export default {
|
|||
frequency: __('Frequency'),
|
||||
},
|
||||
components: {
|
||||
GlAlert,
|
||||
GlEmptyState,
|
||||
GlButton,
|
||||
GlDropdown,
|
||||
|
@ -81,6 +86,7 @@ export default {
|
|||
GlLoadingIcon,
|
||||
GlTable,
|
||||
GlFormInput,
|
||||
GlSprintf,
|
||||
GlPagination,
|
||||
TimeAgo,
|
||||
ErrorTrackingActions,
|
||||
|
@ -117,12 +123,17 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
showIntegratedTrackingDisabledAlert: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
},
|
||||
},
|
||||
hasLocalStorage: AccessorUtils.canUseLocalStorage(),
|
||||
data() {
|
||||
return {
|
||||
errorSearchQuery: '',
|
||||
pageValue: this.$options.FIRST_PAGE,
|
||||
isAlertDismissed: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -142,6 +153,9 @@ export default {
|
|||
errorTrackingHelpUrl() {
|
||||
return helpPagePath('operations/error_tracking');
|
||||
},
|
||||
showIntegratedDisabledAlert() {
|
||||
return !this.isAlertDismissed && this.showIntegratedTrackingDisabledAlert;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
pagination() {
|
||||
|
@ -150,6 +164,8 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
epicLink: 'https://gitlab.com/gitlab-org/gitlab/-/issues/353639',
|
||||
featureFlagLink: helpPagePath('operations/error_tracking'),
|
||||
created() {
|
||||
if (this.errorTrackingEnabled) {
|
||||
this.setEndpoint(this.indexPath);
|
||||
|
@ -232,6 +248,34 @@ export default {
|
|||
<template>
|
||||
<div class="error-list">
|
||||
<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
|
||||
class="row flex-column flex-md-row align-items-md-center m-0 mt-sm-2 p-3 p-sm-3 bg-secondary border"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const severityLevel = {
|
||||
FATAL: 'fatal',
|
||||
ERROR: 'error',
|
||||
WARNING: 'warning',
|
||||
INFO: 'info',
|
||||
DEBUG: 'debug',
|
||||
};
|
||||
|
||||
export const severityLevelVariant = {
|
||||
[severityLevel.FATAL]: 'danger',
|
||||
[severityLevel.ERROR]: 'neutral',
|
||||
[severityLevel.WARNING]: 'warning',
|
||||
[severityLevel.INFO]: 'info',
|
||||
[severityLevel.DEBUG]: 'muted',
|
||||
};
|
||||
|
||||
export const errorStatus = {
|
||||
IGNORED: 'ignored',
|
||||
RESOLVED: 'resolved',
|
||||
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 () => {
|
|||
projectPath,
|
||||
listPath,
|
||||
} = domEl.dataset;
|
||||
let { errorTrackingEnabled, userCanEnableErrorTracking } = domEl.dataset;
|
||||
let {
|
||||
errorTrackingEnabled,
|
||||
userCanEnableErrorTracking,
|
||||
showIntegratedTrackingDisabledAlert,
|
||||
} = domEl.dataset;
|
||||
|
||||
errorTrackingEnabled = parseBoolean(errorTrackingEnabled);
|
||||
userCanEnableErrorTracking = parseBoolean(userCanEnableErrorTracking);
|
||||
showIntegratedTrackingDisabledAlert = parseBoolean(showIntegratedTrackingDisabledAlert);
|
||||
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
|
@ -36,6 +41,7 @@ export default () => {
|
|||
userCanEnableErrorTracking,
|
||||
projectPath,
|
||||
listPath,
|
||||
showIntegratedTrackingDisabledAlert,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
@ -1,29 +1,40 @@
|
|||
<script>
|
||||
import {
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlFormGroup,
|
||||
GlFormCheckbox,
|
||||
GlFormRadioGroup,
|
||||
GlFormRadio,
|
||||
GlFormInputGroup,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { mapActions, mapGetters, mapState } from 'vuex';
|
||||
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 ProjectDropdown from './project_dropdown.vue';
|
||||
|
||||
export default {
|
||||
i18n: I18N_ERROR_TRACKING_SETTINGS,
|
||||
components: {
|
||||
ErrorTrackingForm,
|
||||
GlAlert,
|
||||
GlButton,
|
||||
GlFormCheckbox,
|
||||
GlFormGroup,
|
||||
GlFormRadioGroup,
|
||||
GlFormRadio,
|
||||
GlFormInputGroup,
|
||||
GlLink,
|
||||
GlSprintf,
|
||||
ProjectDropdown,
|
||||
ClipboardButton,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
props: {
|
||||
initialApiHost: {
|
||||
type: String,
|
||||
|
@ -62,6 +73,11 @@ export default {
|
|||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAlertDismissed: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'dropdownLabel',
|
||||
|
@ -81,12 +97,34 @@ export default {
|
|||
showGitlabDsnSetting() {
|
||||
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() {
|
||||
this.setInitialState({
|
||||
apiHost: this.initialApiHost,
|
||||
enabled: this.initialEnabled,
|
||||
integrated: this.initialIntegrated,
|
||||
enabled: this.setInitialEnabled,
|
||||
integrated: this.showIntegratedErrorTracking && this.initialIntegrated,
|
||||
project: this.initialProject,
|
||||
token: this.initialToken,
|
||||
listProjectsEndpoint: this.listProjectsEndpoint,
|
||||
|
@ -104,21 +142,41 @@ export default {
|
|||
handleSubmit() {
|
||||
this.updateSettings();
|
||||
},
|
||||
dismissAlert() {
|
||||
this.isAlertDismissed = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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
|
||||
:label="s__('ErrorTracking|Enable error tracking')"
|
||||
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') }}
|
||||
</gl-form-checkbox>
|
||||
</gl-form-group>
|
||||
<gl-form-group
|
||||
v-if="showIntegratedErrorTracking"
|
||||
:label="s__('ErrorTracking|Error tracking backend')"
|
||||
data-testid="tracking-backend-settings"
|
||||
>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
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.',
|
||||
),
|
||||
};
|
|
@ -4,17 +4,6 @@ module MembershipActions
|
|||
include MembersPresentation
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def create
|
||||
create_params = params.permit(:user_ids, :access_level, :expires_at)
|
||||
result = Members::CreateService.new(current_user, create_params.merge({ source: membershipable, invite_source: "#{plain_source_type}-members-page" })).execute
|
||||
|
||||
if result[:status] == :success
|
||||
redirect_to members_page_url, notice: _('Users were successfully added.')
|
||||
else
|
||||
redirect_to members_page_url, alert: result[:message]
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
update_params = params.require(root_params_key).permit(:access_level, :expires_at)
|
||||
member = membershipable.members_and_requesters.find(params[:id])
|
||||
|
|
|
@ -16,7 +16,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
|
|||
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
|
||||
|
||||
skip_before_action :check_two_factor_requirement, only: :leave
|
||||
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
|
||||
skip_cross_project_access_check :index, :update, :destroy, :request_access,
|
||||
:approve_access_request, :leave, :resend_invite,
|
||||
:override
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
|
|||
before_action :authorize_read_sentry_issue!
|
||||
before_action :set_issue_id, only: :details
|
||||
|
||||
before_action only: [:index] do
|
||||
push_frontend_feature_flag(:integrated_error_tracking, project)
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
|
@ -7,6 +7,10 @@ module Projects
|
|||
before_action :authorize_admin_operations!
|
||||
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]
|
||||
|
||||
helper_method :error_tracking_setting
|
||||
|
|
|
@ -12,7 +12,8 @@ module Projects::ErrorTrackingHelper
|
|||
'error-tracking-enabled' => error_tracking_enabled.to_s,
|
||||
'project-path' => project.full_path,
|
||||
'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
|
||||
|
||||
|
@ -27,4 +28,15 @@ module Projects::ErrorTrackingHelper
|
|||
'issue-stack-trace-path' => stack_trace_project_error_tracking_index_path(*opts)
|
||||
}
|
||||
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
|
||||
|
|
|
@ -59,6 +59,10 @@ module ErrorTracking
|
|||
integrated
|
||||
end
|
||||
|
||||
def integrated_enabled?
|
||||
enabled? && integrated_client?
|
||||
end
|
||||
|
||||
def gitlab_dsn
|
||||
strong_memoize(:gitlab_dsn) do
|
||||
client_key&.sentry_dsn
|
||||
|
|
|
@ -216,10 +216,6 @@ class Integration < ApplicationRecord
|
|||
self.supported_events.map { |event| IntegrationsHelper.integration_event_field_name(event) }
|
||||
end
|
||||
|
||||
def self.supported_event_actions
|
||||
%w[]
|
||||
end
|
||||
|
||||
def self.supported_events
|
||||
%w[commit push tag_push issue confidential_issue merge_request wiki_page]
|
||||
end
|
||||
|
@ -501,10 +497,6 @@ class Integration < ApplicationRecord
|
|||
end
|
||||
end
|
||||
|
||||
def configurable_event_actions
|
||||
self.class.supported_event_actions
|
||||
end
|
||||
|
||||
def supported_events
|
||||
self.class.supported_events
|
||||
end
|
||||
|
|
|
@ -80,10 +80,6 @@ module Integrations
|
|||
%w(commit merge_request)
|
||||
end
|
||||
|
||||
def self.supported_event_actions
|
||||
%w(comment)
|
||||
end
|
||||
|
||||
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
|
||||
def self.reference_pattern(only_long: true)
|
||||
@reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
|
||||
|
|
|
@ -48,10 +48,6 @@ module Integrations
|
|||
%w()
|
||||
end
|
||||
|
||||
def self.supported_event_actions
|
||||
%w()
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
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
|
|
@ -94,7 +94,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
|
|||
|
||||
concerns :clusterable
|
||||
|
||||
resources :group_members, only: [:index, :create, :update, :destroy], concerns: :access_requestable do
|
||||
resources :group_members, only: [:index, :update, :destroy], concerns: :access_requestable do
|
||||
post :resend_invite, on: :member
|
||||
delete :leave, on: :collection
|
||||
end
|
||||
|
|
|
@ -163,7 +163,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
|
|||
end
|
||||
end
|
||||
|
||||
resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+:]+} }, concerns: :access_requestable do
|
||||
resources :project_members, except: [:show, :new, :create, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+:]+} }, concerns: :access_requestable do
|
||||
collection do
|
||||
delete :leave
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSecurityTrainingProviders < Gitlab::Database::Migration[1.0]
|
||||
KONTRA_DATA = {
|
||||
name: 'Kontra',
|
||||
description: "Kontra Application Security provides interactive developer security education that
|
||||
enables engineers to quickly learn security best practices
|
||||
and fix issues in their code by analysing real-world software security vulnerabilities.",
|
||||
url: "https://application.security/api/webhook/gitlab/exercises/search"
|
||||
}
|
||||
|
||||
SCW_DATA = {
|
||||
name: 'Secure Code Warrior',
|
||||
description: "Resolve vulnerabilities faster and confidently with highly relevant and bite-sized secure coding learning.",
|
||||
url: "https://integration-api.securecodewarrior.com/api/v1/trial"
|
||||
}
|
||||
|
||||
module Security
|
||||
class TrainingProvider < ActiveRecord::Base
|
||||
self.table_name = 'security_training_providers'
|
||||
end
|
||||
end
|
||||
|
||||
def up
|
||||
current_time = Time.current
|
||||
timestamps = { created_at: current_time, updated_at: current_time }
|
||||
|
||||
Security::TrainingProvider.reset_column_information
|
||||
|
||||
# upsert providers
|
||||
Security::TrainingProvider.upsert_all([KONTRA_DATA.merge(timestamps), SCW_DATA.merge(timestamps)])
|
||||
end
|
||||
|
||||
def down
|
||||
Security::TrainingProvider.reset_column_information
|
||||
|
||||
Security::TrainingProvider.find_by(name: KONTRA_DATA[:name])&.destroy
|
||||
Security::TrainingProvider.find_by(name: SCW_DATA[:name])&.destroy
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
8a0e80b6df1d942e5ec23641c935103cddd96c044e2a960b88bb38284cf4d070
|
|
@ -14416,6 +14416,10 @@ four standard [pagination arguments](#connection-pagination-arguments):
|
|||
|
||||
Network Policies of the project.
|
||||
|
||||
WARNING:
|
||||
**Deprecated** in 14.8.
|
||||
Network policies are deprecated and will be removed in GitLab 15.0.
|
||||
|
||||
Returns [`NetworkPolicyConnection`](#networkpolicyconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
|
|
|
@ -159,25 +159,22 @@ When the number of RuboCop exceptions exceed the default [`exclude-limit` of 15]
|
|||
we may want to resolve exceptions over multiple commits. To minimize confusion,
|
||||
we should track our progress through the exception list.
|
||||
|
||||
When auto-generating the `.rubocop_todo.yml` exception list for a particular Cop,
|
||||
and more than 15 files are affected, we should add the exception list to
|
||||
a different file in the directory `.rubocop_todo/`. For example, the configuration for the cop
|
||||
`Gitlab/NamespacedClass` is in `.rubocop_todo/gitlab/namespaced_class.yml`.
|
||||
|
||||
This ensures that our list isn't mistakenly removed by another auto generation of
|
||||
the `.rubocop_todo.yml`. This also allows us greater visibility into the exceptions
|
||||
which are currently being resolved.
|
||||
|
||||
One way to generate the initial list is to run the Rake task `rubocop:todo:generate`:
|
||||
The preferred way to [generate the initial list or a list for specific RuboCop rules](../rake_tasks.md#generate-initial-rubocop-todo-list)
|
||||
is to run the Rake task `rubocop:todo:generate`:
|
||||
|
||||
```shell
|
||||
# Initial list
|
||||
bundle exec rake rubocop:todo:generate
|
||||
|
||||
# List for specific RuboCop rules
|
||||
bundle exec rake 'rubocop:todo:generate[Gitlab/NamespacedClass,Lint/Syntax]'
|
||||
```
|
||||
|
||||
You can then move the list from the freshly generated `.rubocop_todo.yml` for the Cop being actively
|
||||
resolved and place it in the directory `.rubocop_todo/`. In this scenario, do not commit
|
||||
auto-generated changes to the `.rubocop_todo.yml`, as an `exclude limit` that is higher than 15
|
||||
makes the `.rubocop_todo.yml` hard to parse.
|
||||
This Rake task creates or updates the exception list in `.rubocop_todo/`. For
|
||||
example, the configuration for the RuboCop rule `Gitlab/NamespacedClass` is
|
||||
located in `.rubocop_todo/gitlab/namespaced_class.yml`.
|
||||
|
||||
Make sure to commit any changes in `.rubocop_todo/` after running the Rake task.
|
||||
|
||||
### Reveal existing RuboCop exceptions
|
||||
|
||||
|
|
|
@ -196,6 +196,16 @@ One way to generate the initial list is to run the Rake task `rubocop:todo:gener
|
|||
bundle exec rake rubocop:todo:generate
|
||||
```
|
||||
|
||||
To generate TODO list for specific RuboCop rules, pass them comma-seperated as
|
||||
argument to the Rake task:
|
||||
|
||||
```shell
|
||||
bundle exec rake 'rubocop:todo:generate[Gitlab/NamespacedClass,Lint/Syntax]'
|
||||
bundle exec rake rubocop:todo:generate\[Gitlab/NamespacedClass,Lint/Syntax\]
|
||||
```
|
||||
|
||||
Some shells require brackets to be escaped or quoted.
|
||||
|
||||
See [Resolving RuboCop exceptions](contributing/style_guides.md#resolving-rubocop-exceptions)
|
||||
on how to proceed from here.
|
||||
|
||||
|
|
|
@ -129,7 +129,17 @@ If another event occurs, the error reverts to unresolved.
|
|||
|
||||
## 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.
|
||||
You still use Sentry SDK with your application. But you don't need to deploy Sentry
|
||||
|
|
|
@ -27,10 +27,9 @@ On the vulnerability's page, you can:
|
|||
- [Change the vulnerability's status](#change-vulnerability-status).
|
||||
- [Create an issue](#create-an-issue-for-a-vulnerability).
|
||||
- [Link issues to the vulnerability](#linked-issues).
|
||||
- [Resolve a vulnerability](#resolve-a-vulnerability), if a solution is
|
||||
available.
|
||||
|
||||
In GitLab 14.9 and later, if security training is enabled, the vulnerability page includes a training link relevant to the detected vulnerability.
|
||||
- [Resolve a vulnerability](#resolve-a-vulnerability) if a solution is
|
||||
available.
|
||||
- [View security training specific to the detected vulnerability](#view-security-training-for-a-vulnerability).
|
||||
|
||||
## Vulnerability status values
|
||||
|
||||
|
@ -177,9 +176,13 @@ To enable security training for vulnerabilities in your project:
|
|||
|
||||
## View security training for a vulnerability
|
||||
|
||||
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/6176) in GitLab 14.9.
|
||||
|
||||
If security training is enabled, the vulnerability page includes a training link relevant to the detected vulnerability.
|
||||
|
||||
To view the security training for a vulnerability:
|
||||
|
||||
1. On the top bar, select **Menu > Projects** and find your project.
|
||||
1. On the left sidebar, select **Security & Compliance > Vulnerability report**.
|
||||
1. Select the vulnerability for which you want to view security training.
|
||||
1. If the security training provider supports training for the vulnerability, select **View training**.
|
||||
1. Select **View training**.
|
||||
|
|
|
@ -9,6 +9,12 @@ module API
|
|||
expose :sentry_external_url
|
||||
expose :api_url
|
||||
expose :integrated
|
||||
|
||||
def integrated
|
||||
return false unless ::Feature.enabled?(:integrated_error_tracking, object.project)
|
||||
|
||||
object.integrated_client?
|
||||
end
|
||||
end
|
||||
|
||||
class ClientKey < Grape::Entity
|
||||
|
|
|
@ -28,8 +28,8 @@ module API
|
|||
end
|
||||
|
||||
def feature_enabled?
|
||||
project.error_tracking_setting&.enabled? &&
|
||||
project.error_tracking_setting&.integrated_client?
|
||||
Feature.enabled?(:integrated_error_tracking, project) &&
|
||||
project.error_tracking_setting&.integrated_enabled?
|
||||
end
|
||||
|
||||
def find_client_key(public_key)
|
||||
|
|
|
@ -13,12 +13,14 @@ module Gitlab
|
|||
end
|
||||
|
||||
def successful?(result)
|
||||
result == '1'
|
||||
result == Gitlab::Database.database_base_models.size
|
||||
end
|
||||
|
||||
def check
|
||||
catch_timeout 10.seconds do
|
||||
ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')&.to_s
|
||||
Gitlab::Database.database_base_models.sum do |_, base|
|
||||
base.connection.select_value('SELECT 1')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable Rails/RakeEnvironment
|
||||
|
||||
unless Rails.env.production?
|
||||
require 'rubocop/rake_task'
|
||||
|
@ -8,18 +9,59 @@ unless Rails.env.production?
|
|||
namespace :rubocop do
|
||||
namespace :todo do
|
||||
desc 'Generate RuboCop todos'
|
||||
task :generate do # rubocop:disable Rails/RakeEnvironment
|
||||
task :generate do |_task, args|
|
||||
require 'rubocop'
|
||||
require 'active_support/inflector/inflections'
|
||||
require_relative '../../rubocop/todo_dir'
|
||||
require_relative '../../rubocop/formatter/todo_formatter'
|
||||
|
||||
# Reveal all pending TODOs so RuboCop can pick them up and report
|
||||
# during scan.
|
||||
ENV['REVEAL_RUBOCOP_TODO'] = '1'
|
||||
|
||||
# Save cop configuration like `RSpec/ContextWording` into
|
||||
# `rspec/context_wording.yml` and not into
|
||||
# `r_spec/context_wording.yml`.
|
||||
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
||||
inflect.acronym 'RSpec'
|
||||
inflect.acronym 'GraphQL'
|
||||
end
|
||||
|
||||
options = %w[
|
||||
--auto-gen-config
|
||||
--auto-gen-only-exclude
|
||||
--exclude-limit=100000
|
||||
--no-offense-counts
|
||||
--parallel
|
||||
--format RuboCop::Formatter::TodoFormatter
|
||||
]
|
||||
|
||||
# Convert from Rake::TaskArguments into an Array to make `any?` work as
|
||||
# expected.
|
||||
cop_names = args.to_a
|
||||
|
||||
todo_dir = RuboCop::TodoDir.new(RuboCop::TodoDir::DEFAULT_TODO_DIR)
|
||||
|
||||
if cop_names.any?
|
||||
# We are sorting the cop names to benefit from RuboCop cache which
|
||||
# also takes passed parameters into account.
|
||||
list = cop_names.sort.join(',')
|
||||
options.concat ['--only', list]
|
||||
|
||||
cop_names.each { |cop_name| todo_dir.inspect(cop_name) }
|
||||
else
|
||||
todo_dir.inspect_all
|
||||
end
|
||||
|
||||
puts <<~MSG
|
||||
Generating RuboCop TODOs with:
|
||||
rubocop #{options.join(' ')}
|
||||
|
||||
This might take a while...
|
||||
MSG
|
||||
|
||||
RuboCop::CLI.new.run(options)
|
||||
|
||||
todo_dir.delete_inspected
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Rails/RakeEnvironment
|
||||
|
|
|
@ -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"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
@ -14452,6 +14458,9 @@ msgstr ""
|
|||
msgid "ErrorTracking|To enable project selection, enter a valid Auth Token."
|
||||
msgstr ""
|
||||
|
||||
msgid "ErrorTracking|View project settings"
|
||||
msgstr ""
|
||||
|
||||
msgid "Errors"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module QA
|
|||
select :number_of_employees
|
||||
text_field :telephone_number
|
||||
select :country
|
||||
select :state, id: 'state'
|
||||
select :state
|
||||
button :continue
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,12 +6,11 @@ module QA
|
|||
class Select < Chemlab::Page
|
||||
path '/-/trials/select'
|
||||
|
||||
# TODO: Supplant with data-qa-selectors
|
||||
select :subscription_for, id: 'namespace_id'
|
||||
text_field :new_group_name, id: 'new_group_name'
|
||||
button :start_your_free_trial, value: 'Start your free trial'
|
||||
radio :trial_company, id: 'trial_entity_company'
|
||||
radio :trial_individual, id: 'trial_entity_individual'
|
||||
select :subscription_for
|
||||
text_field :new_group_name
|
||||
button :start_your_free_trial
|
||||
radio :trial_company
|
||||
radio :trial_individual
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,7 +95,7 @@ module QA
|
|||
def wait_until_forked
|
||||
Runtime::Logger.debug("Waiting for the fork process to complete...")
|
||||
forked = wait_until do
|
||||
project.import_status == "finished"
|
||||
project.reload!.import_status == "finished"
|
||||
end
|
||||
|
||||
raise "Timed out while waiting for the fork process to complete." unless forked
|
||||
|
|
|
@ -47,9 +47,8 @@ module QA
|
|||
|
||||
def remove_via_api!
|
||||
runners = project.runners(tag_list: @tags)
|
||||
unless runners && !runners.empty?
|
||||
raise "Project #{project.path_with_namespace} has no runners#{" with tags #{@tags}." if @tags&.any?}"
|
||||
end
|
||||
|
||||
return if runners.blank?
|
||||
|
||||
this_runner = runners.find { |runner| runner[:description] == name }
|
||||
unless this_runner
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Verify', :runner do
|
||||
describe 'Runner removal' do
|
||||
include Support::API
|
||||
|
||||
let(:api_client) { Runtime::API::Client.new(:gitlab) }
|
||||
let(:executor) { "qa-runner-#{Time.now.to_i}" }
|
||||
let(:runner_tags) { ['runner-registration-e2e-test'] }
|
||||
let!(:runner) do
|
||||
Resource::Runner.fabricate! do |runner|
|
||||
runner.name = executor
|
||||
runner.tags = runner_tags
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
sleep 5 # Runner should register within 5 seconds
|
||||
end
|
||||
|
||||
# Removing a runner via the UI is covered by `spec/features/runners_spec.rb``
|
||||
it 'removes the runner' do
|
||||
expect(runner.project.runners.size).to eq(1)
|
||||
expect(runner.project.runners.first[:description]).to eq(executor)
|
||||
|
||||
request = Runtime::API::Request.new(api_client, "runners/#{runner.project.runners.first[:id]}")
|
||||
response = delete(request.url)
|
||||
expect(response.code).to eq(Support::API::HTTP_STATUS_NO_CONTENT)
|
||||
expect(response.body).to be_empty
|
||||
|
||||
expect(runner.project.runners).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,138 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'set'
|
||||
require 'rubocop'
|
||||
require 'yaml'
|
||||
|
||||
require_relative '../todo_dir'
|
||||
|
||||
module RuboCop
|
||||
module Formatter
|
||||
# This formatter dumps a YAML configuration file per cop rule
|
||||
# into `.rubocop_todo/**/*.yml` which contains detected offenses.
|
||||
#
|
||||
# For example, this formatter stores offenses for `RSpec/VariableName`
|
||||
# in `.rubocop_todo/rspec/variable_name.yml`.
|
||||
class TodoFormatter < BaseFormatter
|
||||
# Disable a cop which exceeds this limit. This way we ensure that we
|
||||
# don't enable a cop by accident when moving it from
|
||||
# .rubocop_todo.yml to .rubocop_todo/.
|
||||
# We keep the cop disabled if it has been disabled previously explicitly
|
||||
# via `Enabled: false` in .rubocop_todo.yml or .rubocop_todo/.
|
||||
MAX_OFFENSE_COUNT = 15
|
||||
|
||||
Todo = Struct.new(:cop_name, :files, :offense_count) do
|
||||
def initialize(cop_name)
|
||||
super(cop_name, Set.new, 0)
|
||||
|
||||
@cop_class = RuboCop::Cop::Registry.global.find_by_cop_name(cop_name)
|
||||
end
|
||||
|
||||
def record(file, offense_count)
|
||||
files << file
|
||||
self.offense_count += offense_count
|
||||
end
|
||||
|
||||
def autocorrectable?
|
||||
@cop_class&.support_autocorrect?
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(output, options = {})
|
||||
directory = options.delete(:rubocop_todo_dir) || TodoDir::DEFAULT_TODO_DIR
|
||||
@todos = Hash.new { |hash, cop_name| hash[cop_name] = Todo.new(cop_name) }
|
||||
@todo_dir = TodoDir.new(directory)
|
||||
@config_inspect_todo_dir = load_config_inspect_todo_dir(directory)
|
||||
@config_old_todo_yml = load_config_old_todo_yml(directory)
|
||||
check_multiple_configurations!
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
def file_finished(file, offenses)
|
||||
return if offenses.empty?
|
||||
|
||||
file = relative_path(file)
|
||||
|
||||
offenses.map(&:cop_name).tally.each do |cop_name, offense_count|
|
||||
@todos[cop_name].record(file, offense_count)
|
||||
end
|
||||
end
|
||||
|
||||
def finished(_inspected_files)
|
||||
@todos.values.sort_by(&:cop_name).each do |todo|
|
||||
yaml = to_yaml(todo)
|
||||
path = @todo_dir.write(todo.cop_name, yaml)
|
||||
|
||||
output.puts "Written to #{relative_path(path)}\n"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def relative_path(path)
|
||||
parent = File.expand_path('..', @todo_dir.directory)
|
||||
path.delete_prefix("#{parent}/")
|
||||
end
|
||||
|
||||
def to_yaml(todo)
|
||||
yaml = []
|
||||
yaml << '---'
|
||||
yaml << '# Cop supports --auto-correct.' if todo.autocorrectable?
|
||||
yaml << "#{todo.cop_name}:"
|
||||
|
||||
if previously_disabled?(todo) && offense_count_exceeded?(todo)
|
||||
yaml << " # Offense count: #{todo.offense_count}"
|
||||
yaml << ' # Temporarily disabled due to too many offenses'
|
||||
yaml << ' Enabled: false'
|
||||
end
|
||||
|
||||
yaml << ' Exclude:'
|
||||
|
||||
files = todo.files.sort.map { |file| " - '#{file}'" }
|
||||
yaml.concat files
|
||||
yaml << ''
|
||||
|
||||
yaml.join("\n")
|
||||
end
|
||||
|
||||
def offense_count_exceeded?(todo)
|
||||
todo.offense_count > MAX_OFFENSE_COUNT
|
||||
end
|
||||
|
||||
def check_multiple_configurations!
|
||||
cop_names = @config_inspect_todo_dir.keys & @config_old_todo_yml.keys
|
||||
return if cop_names.empty?
|
||||
|
||||
list = cop_names.sort.map { |cop_name| "- #{cop_name}" }.join("\n")
|
||||
raise "Multiple configurations found for cops:\n#{list}\n"
|
||||
end
|
||||
|
||||
def previously_disabled?(todo)
|
||||
cop_name = todo.cop_name
|
||||
|
||||
config = @config_old_todo_yml[cop_name] ||
|
||||
@config_inspect_todo_dir[cop_name] || {}
|
||||
return false if config.empty?
|
||||
|
||||
config['Enabled'] == false
|
||||
end
|
||||
|
||||
def load_config_inspect_todo_dir(directory)
|
||||
@todo_dir.list_inspect.each_with_object({}) do |path, combined|
|
||||
config = YAML.load_file(path)
|
||||
combined.update(config) if Hash === config
|
||||
end
|
||||
end
|
||||
|
||||
# Load YAML configuration from `.rubocop_todo.yml`.
|
||||
# We consider this file already old, obsolete, and to be removed soon.
|
||||
def load_config_old_todo_yml(directory)
|
||||
path = File.expand_path(File.join(directory, '../.rubocop_todo.yml'))
|
||||
config = YAML.load_file(path) if File.exist?(path)
|
||||
|
||||
config || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fileutils'
|
||||
require 'active_support/inflector/inflections'
|
||||
|
||||
module RuboCop
|
||||
# Helper class to manage file access to RuboCop TODOs in .rubocop_todo directory.
|
||||
class TodoDir
|
||||
DEFAULT_TODO_DIR = File.expand_path('../.rubocop_todo', __dir__)
|
||||
|
||||
# Suffix to indicate TODOs being inspected right now.
|
||||
SUFFIX_INSPECT = '.inspect'
|
||||
|
||||
attr_reader :directory
|
||||
|
||||
def initialize(directory, inflector: ActiveSupport::Inflector)
|
||||
@directory = directory
|
||||
@inflector = inflector
|
||||
end
|
||||
|
||||
def read(cop_name, suffix = nil)
|
||||
read_suffixed(cop_name)
|
||||
end
|
||||
|
||||
def write(cop_name, content)
|
||||
path = path_for(cop_name)
|
||||
|
||||
FileUtils.mkdir_p(File.dirname(path))
|
||||
File.write(path, content)
|
||||
|
||||
path
|
||||
end
|
||||
|
||||
def inspect(cop_name)
|
||||
path = path_for(cop_name)
|
||||
|
||||
if File.exist?(path)
|
||||
FileUtils.mv(path, "#{path}#{SUFFIX_INSPECT}")
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def inspect_all
|
||||
pattern = File.join(@directory, '**/*.yml')
|
||||
|
||||
Dir.glob(pattern).count do |path|
|
||||
FileUtils.mv(path, "#{path}#{SUFFIX_INSPECT}")
|
||||
end
|
||||
end
|
||||
|
||||
def list_inspect
|
||||
pattern = File.join(@directory, "**/*.yml.inspect")
|
||||
|
||||
Dir.glob(pattern)
|
||||
end
|
||||
|
||||
def delete_inspected
|
||||
pattern = File.join(@directory, '**/*.yml.inspect')
|
||||
|
||||
Dir.glob(pattern).count do |path|
|
||||
File.delete(path)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_suffixed(cop_name, suffix = nil)
|
||||
path = path_for(cop_name, suffix)
|
||||
|
||||
File.read(path) if File.exist?(path)
|
||||
end
|
||||
|
||||
def path_for(cop_name, suffix = nil)
|
||||
todo_path = "#{@inflector.underscore(cop_name)}.yml#{suffix}"
|
||||
|
||||
File.join(@directory, todo_path)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -99,107 +99,6 @@ RSpec.describe Groups::GroupMembersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let_it_be(:group_user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when user does not have enough rights' do
|
||||
before do
|
||||
group.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns 403', :aggregate_failures do
|
||||
post :create, params: {
|
||||
group_id: group,
|
||||
user_ids: group_user.id,
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:forbidden)
|
||||
expect(group.users).not_to include group_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has enough rights' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
it 'adds user to members', :aggregate_failures, :snowplow do
|
||||
post :create, params: {
|
||||
group_id: group,
|
||||
user_ids: group_user.id,
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(controller).to set_flash.to 'Users were successfully added.'
|
||||
expect(response).to redirect_to(group_group_members_path(group))
|
||||
expect(group.users).to include group_user
|
||||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'group-members-page',
|
||||
property: 'existing_user',
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds no user to members', :aggregate_failures do
|
||||
post :create, params: {
|
||||
group_id: group,
|
||||
user_ids: '',
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(controller).to set_flash.to 'No users specified.'
|
||||
expect(response).to redirect_to(group_group_members_path(group))
|
||||
expect(group.users).not_to include group_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'access expiry date' do
|
||||
before do
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
subject do
|
||||
post :create, params: {
|
||||
group_id: group,
|
||||
user_ids: group_user.id,
|
||||
access_level: Gitlab::Access::GUEST,
|
||||
expires_at: expires_at
|
||||
}
|
||||
end
|
||||
|
||||
context 'when set to a date in the past' do
|
||||
let(:expires_at) { 2.days.ago }
|
||||
|
||||
it 'does not add user to members', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(flash[:alert]).to include('Expires at cannot be a date in the past')
|
||||
expect(response).to redirect_to(group_group_members_path(group))
|
||||
expect(group.users).not_to include group_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when set to a date in the future' do
|
||||
let(:expires_at) { 5.days.from_now }
|
||||
|
||||
it 'adds user to members', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(controller).to set_flash.to 'Users were successfully added.'
|
||||
expect(response).to redirect_to(group_group_members_path(group))
|
||||
expect(group.users).to include group_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
let_it_be(:requester) { create(:group_member, :access_request, group: group) }
|
||||
|
||||
|
@ -508,14 +407,6 @@ RSpec.describe Groups::GroupMembersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
it 'is successful' do
|
||||
post :create, params: { group_id: group, users: user, access_level: Gitlab::Access::GUEST }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
it 'is successful' do
|
||||
put :update,
|
||||
|
|
|
@ -147,137 +147,6 @@ RSpec.describe Projects::ProjectMembersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let_it_be(:project_user) { create(:user) }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when user does not have enough rights' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'returns 404', :aggregate_failures do
|
||||
post :create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
user_ids: project_user.id,
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(project.users).not_to include project_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has enough rights' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'adds user to members', :aggregate_failures, :snowplow do
|
||||
post :create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
user_ids: project_user.id,
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(controller).to set_flash.to 'Users were successfully added.'
|
||||
expect(response).to redirect_to(project_project_members_path(project))
|
||||
expect(project.users).to include project_user
|
||||
expect_snowplow_event(
|
||||
category: 'Members::CreateService',
|
||||
action: 'create_member',
|
||||
label: 'project-members-page',
|
||||
property: 'existing_user',
|
||||
user: user
|
||||
)
|
||||
end
|
||||
|
||||
it 'adds no user to members', :aggregate_failures do
|
||||
expect_next_instance_of(Members::CreateService) do |instance|
|
||||
expect(instance).to receive(:execute).and_return(status: :failure, message: 'Message')
|
||||
end
|
||||
|
||||
post :create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
user_ids: '',
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(controller).to set_flash.to 'Message'
|
||||
expect(response).to redirect_to(project_project_members_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'adding project bot' do
|
||||
let_it_be(:project_bot) { create(:user, :project_bot) }
|
||||
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
||||
unrelated_project = create(:project)
|
||||
unrelated_project.add_maintainer(project_bot)
|
||||
end
|
||||
|
||||
it 'returns error', :aggregate_failures do
|
||||
post :create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
user_ids: project_bot.id,
|
||||
access_level: Gitlab::Access::GUEST
|
||||
}
|
||||
|
||||
expect(flash[:alert]).to include('project bots cannot be added to other groups / projects')
|
||||
expect(response).to redirect_to(project_project_members_path(project))
|
||||
end
|
||||
end
|
||||
|
||||
context 'access expiry date' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
end
|
||||
|
||||
subject do
|
||||
post :create, params: {
|
||||
namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
user_ids: project_user.id,
|
||||
access_level: Gitlab::Access::GUEST,
|
||||
expires_at: expires_at
|
||||
}
|
||||
end
|
||||
|
||||
context 'when set to a date in the past' do
|
||||
let(:expires_at) { 2.days.ago }
|
||||
|
||||
it 'does not add user to members', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(flash[:alert]).to include('Expires at cannot be a date in the past')
|
||||
expect(response).to redirect_to(project_project_members_path(project))
|
||||
expect(project.users).not_to include project_user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when set to a date in the future' do
|
||||
let(:expires_at) { 5.days.from_now }
|
||||
|
||||
it 'adds user to members', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(controller).to set_flash.to 'Users were successfully added.'
|
||||
expect(response).to redirect_to(project_project_members_path(project))
|
||||
expect(project.users).to include project_user
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
let_it_be(:requester) { create(:project_member, :access_request, project: project) }
|
||||
|
||||
|
@ -656,48 +525,6 @@ RSpec.describe Projects::ProjectMembersController do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let_it_be(:stranger) { create(:user) }
|
||||
|
||||
context 'when creating owner' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'creates a member' do
|
||||
expect do
|
||||
post :create, params: {
|
||||
user_ids: stranger.id,
|
||||
namespace_id: project.namespace,
|
||||
access_level: Member::OWNER,
|
||||
project_id: project
|
||||
}
|
||||
end.to change { project.members.count }.by(1)
|
||||
|
||||
expect(project.team_members).to include(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when create maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'creates a member' do
|
||||
expect do
|
||||
post :create, params: {
|
||||
user_ids: stranger.id,
|
||||
namespace_id: project.namespace,
|
||||
access_level: Member::MAINTAINER,
|
||||
project_id: project
|
||||
}
|
||||
end.to change { project.members.count }.by(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST resend_invite' do
|
||||
let_it_be(:member) { create(:project_member, project: project) }
|
||||
|
||||
|
|
|
@ -10,11 +10,7 @@ import {
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import {
|
||||
severityLevel,
|
||||
severityLevelVariant,
|
||||
errorStatus,
|
||||
} from '~/error_tracking/components/constants';
|
||||
import { severityLevel, severityLevelVariant, errorStatus } from '~/error_tracking/constants';
|
||||
import ErrorDetails from '~/error_tracking/components/error_details.vue';
|
||||
import Stacktrace from '~/error_tracking/components/stacktrace.vue';
|
||||
import {
|
||||
|
|
|
@ -7,6 +7,7 @@ import ErrorTrackingActions from '~/error_tracking/components/error_tracking_act
|
|||
import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
|
||||
import { trackErrorListViewsOptions, trackErrorStatusUpdateOptions } from '~/error_tracking/utils';
|
||||
import Tracking from '~/tracking';
|
||||
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
|
||||
import errorsList from './list_mock.json';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
@ -25,28 +26,33 @@ describe('ErrorTrackingList', () => {
|
|||
const findLoadingIcon = () => wrapper.find(GlLoadingIcon);
|
||||
const findPagination = () => wrapper.find(GlPagination);
|
||||
const findErrorActions = () => wrapper.find(ErrorTrackingActions);
|
||||
const findIntegratedDisabledAlert = () => wrapper.findByTestId('integrated-disabled-alert');
|
||||
|
||||
function mountComponent({
|
||||
errorTrackingEnabled = true,
|
||||
userCanEnableErrorTracking = true,
|
||||
showIntegratedTrackingDisabledAlert = false,
|
||||
stubs = {},
|
||||
} = {}) {
|
||||
wrapper = mount(ErrorTrackingList, {
|
||||
store,
|
||||
propsData: {
|
||||
indexPath: '/path',
|
||||
listPath: '/error_tracking',
|
||||
projectPath: 'project/test',
|
||||
enableErrorTrackingLink: '/link',
|
||||
userCanEnableErrorTracking,
|
||||
errorTrackingEnabled,
|
||||
illustrationPath: 'illustration/path',
|
||||
},
|
||||
stubs: {
|
||||
...stubChildren(ErrorTrackingList),
|
||||
...stubs,
|
||||
},
|
||||
});
|
||||
wrapper = extendedWrapper(
|
||||
mount(ErrorTrackingList, {
|
||||
store,
|
||||
propsData: {
|
||||
indexPath: '/path',
|
||||
listPath: '/error_tracking',
|
||||
projectPath: 'project/test',
|
||||
enableErrorTrackingLink: '/link',
|
||||
userCanEnableErrorTracking,
|
||||
errorTrackingEnabled,
|
||||
showIntegratedTrackingDisabledAlert,
|
||||
illustrationPath: 'illustration/path',
|
||||
},
|
||||
stubs: {
|
||||
...stubChildren(ErrorTrackingList),
|
||||
...stubs,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -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', () => {
|
||||
beforeEach(() => {
|
||||
store.state.list.loading = false;
|
||||
|
|
|
@ -18,19 +18,27 @@ describe('error tracking settings app', () => {
|
|||
let store;
|
||||
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(
|
||||
shallowMount(ErrorTrackingSettings, {
|
||||
store, // Override the imported store
|
||||
propsData: {
|
||||
initialEnabled: 'true',
|
||||
initialIntegrated: 'false',
|
||||
initialApiHost: TEST_HOST,
|
||||
initialToken: 'someToken',
|
||||
initialProject: null,
|
||||
listProjectsEndpoint: TEST_HOST,
|
||||
operationsSettingsEndpoint: TEST_HOST,
|
||||
gitlabDsn: TEST_GITLAB_DSN,
|
||||
propsData: { ...props },
|
||||
provide: {
|
||||
glFeatures,
|
||||
},
|
||||
stubs: {
|
||||
GlFormInputGroup, // we need this non-shallow to query for a component within a slot
|
||||
|
@ -47,6 +55,7 @@ describe('error tracking settings app', () => {
|
|||
const findElementWithText = (wrappers, text) => wrappers.filter((item) => item.text() === text);
|
||||
const findSentrySettings = () => wrapper.findByTestId('sentry-setting-form');
|
||||
const findDsnSettings = () => wrapper.findByTestId('gitlab-dsn-setting-form');
|
||||
const findEnabledCheckbox = () => wrapper.findByTestId('error-tracking-enabled');
|
||||
|
||||
const enableGitLabErrorTracking = async () => {
|
||||
findBackendSettingsRadioGroup().vm.$emit('change', true);
|
||||
|
@ -88,62 +97,104 @@ describe('error tracking settings app', () => {
|
|||
});
|
||||
|
||||
describe('tracking-backend settings', () => {
|
||||
it('contains a form-group with the correct label', () => {
|
||||
expect(findBackendSettingsSection().attributes('label')).toBe('Error tracking backend');
|
||||
it('does not contain backend settings section', () => {
|
||||
expect(findBackendSettingsSection().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('contains a radio group', () => {
|
||||
expect(findBackendSettingsRadioGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains the correct radio buttons', () => {
|
||||
expect(findBackendSettingsRadioButtons()).toHaveLength(2);
|
||||
|
||||
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 () => {
|
||||
it('shows the sentry form', () => {
|
||||
expect(findSentrySettings().exists()).toBe(true);
|
||||
|
||||
await enableGitLabErrorTracking();
|
||||
|
||||
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);
|
||||
describe('enabled setting is true', () => {
|
||||
describe('integrated setting is true', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
props: { ...defaultProps, initialEnabled: 'true', initialIntegrated: 'true' },
|
||||
});
|
||||
});
|
||||
|
||||
await enableGitLabErrorTracking();
|
||||
|
||||
expect(findDsnSettings().exists()).toBe(true);
|
||||
it('displays enabled as false', () => {
|
||||
expect(findEnabledCheckbox().attributes('checked')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('contains copy-to-clipboard functionality for the GitLab DSN string', async () => {
|
||||
await enableGitLabErrorTracking();
|
||||
describe('integrated setting is false', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
props: { ...defaultProps, initialEnabled: 'true', initialIntegrated: 'false' },
|
||||
});
|
||||
});
|
||||
|
||||
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('displays enabled as true', () => {
|
||||
expect(findEnabledCheckbox().attributes('checked')).toBe('true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.each([true, false])(
|
||||
'calls the `updateIntegrated` action when the setting changes to `%s`',
|
||||
(integrated) => {
|
||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
||||
describe('integrated_error_tracking feature flag enabled', () => {
|
||||
beforeEach(() => {
|
||||
mountComponent({
|
||||
glFeatures: { integratedErrorTracking: true },
|
||||
});
|
||||
});
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(0);
|
||||
it('contains a form-group with the correct label', () => {
|
||||
expect(findBackendSettingsSection().attributes('label')).toBe('Error tracking backend');
|
||||
});
|
||||
|
||||
findBackendSettingsRadioGroup().vm.$emit('change', integrated);
|
||||
it('contains a radio group', () => {
|
||||
expect(findBackendSettingsRadioGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(store.dispatch).toHaveBeenCalledWith('updateIntegrated', integrated);
|
||||
},
|
||||
);
|
||||
it('contains the correct radio buttons', () => {
|
||||
expect(findBackendSettingsRadioButtons()).toHaveLength(2);
|
||||
|
||||
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();
|
||||
|
||||
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])(
|
||||
'calls the `updateIntegrated` action when the setting changes to `%s`',
|
||||
(integrated) => {
|
||||
jest.spyOn(store, 'dispatch').mockImplementation();
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(0);
|
||||
|
||||
findBackendSettingsRadioGroup().vm.$emit('change', integrated);
|
||||
|
||||
expect(store.dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(store.dispatch).toHaveBeenCalledWith('updateIntegrated', integrated);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -34,7 +34,8 @@ RSpec.describe Projects::ErrorTrackingHelper do
|
|||
'error-tracking-enabled' => 'false',
|
||||
'list-path' => list_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
|
||||
|
@ -67,6 +68,37 @@ RSpec.describe Projects::ErrorTrackingHelper do
|
|||
)
|
||||
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
|
||||
|
||||
context 'when user is not maintainer' do
|
||||
|
|
|
@ -4,5 +4,20 @@ require 'spec_helper'
|
|||
require_relative './simple_check_shared'
|
||||
|
||||
RSpec.describe Gitlab::HealthChecks::DbCheck do
|
||||
include_examples 'simple_check', 'db_ping', 'Db', '1'
|
||||
include_examples 'simple_check', 'db_ping', 'Db', Gitlab::Database.database_base_models.size
|
||||
|
||||
context 'with multiple databases' do
|
||||
subject { described_class.readiness }
|
||||
|
||||
before do
|
||||
allow(Gitlab::Database).to receive(:database_base_models)
|
||||
.and_return({ main: ApplicationRecord, ci: Ci::ApplicationRecord }.with_indifferent_access)
|
||||
end
|
||||
|
||||
it 'checks multiple databases' do
|
||||
expect(ApplicationRecord.connection).to receive(:select_value).with('SELECT 1').and_call_original
|
||||
expect(Ci::ApplicationRecord.connection).to receive(:select_value).with('SELECT 1').and_call_original
|
||||
expect(subject).to have_attributes(success: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!
|
||||
|
||||
RSpec.describe AddSecurityTrainingProviders, :migration do
|
||||
include MigrationHelpers::WorkItemTypesHelper
|
||||
|
||||
let_it_be(:security_training_providers) { table(:security_training_providers) }
|
||||
|
||||
it 'creates default data' do
|
||||
# Need to delete all as security training providers are seeded before entire test suite
|
||||
security_training_providers.delete_all
|
||||
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(security_training_providers.count).to eq(0)
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
expect(security_training_providers.count).to eq(2)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -535,6 +535,25 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
|
|||
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
|
||||
let!(:client_key) { create(:error_tracking_client_key, project: project) }
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ RSpec.describe API::ErrorTracking::Collector do
|
|||
RSpec.shared_examples 'successful request' do
|
||||
it 'writes to the database and returns OK', :aggregate_failures do
|
||||
expect { subject }.to change { ErrorTracking::ErrorEvent.count }.by(1)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
end
|
||||
|
@ -42,6 +41,14 @@ RSpec.describe API::ErrorTracking::Collector do
|
|||
|
||||
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
|
||||
before do
|
||||
setting.update!(enabled: false)
|
||||
|
|
|
@ -23,6 +23,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
|
|||
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
|
||||
it 'returns no project settings' do
|
||||
make_request
|
||||
|
@ -46,7 +61,17 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
|
|||
end
|
||||
|
||||
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
|
||||
expect(setting).to be_enabled
|
||||
|
@ -84,13 +109,19 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
|
|||
context 'with integrated param' do
|
||||
let(:params) { { active: true, integrated: true } }
|
||||
|
||||
it 'updates the integrated flag' do
|
||||
expect(setting.integrated).to be_falsey
|
||||
context 'integrated_error_tracking feature enabled' do
|
||||
before do
|
||||
stub_feature_flags(integrated_error_tracking: true)
|
||||
end
|
||||
|
||||
make_request
|
||||
it 'updates the integrated flag' do
|
||||
expect(setting.integrated).to be_falsey
|
||||
|
||||
expect(json_response).to include('integrated' => true)
|
||||
expect(setting.reload.integrated).to be_truthy
|
||||
make_request
|
||||
|
||||
expect(json_response).to include('integrated' => true)
|
||||
expect(setting.reload.integrated).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -170,7 +201,21 @@ RSpec.describe API::ErrorTracking::ProjectSettings do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -208,6 +208,25 @@ RSpec.describe API::Invitations do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when adding project bot' do
|
||||
let_it_be(:project_bot) { create(:user, :project_bot) }
|
||||
|
||||
before do
|
||||
unrelated_project = create(:project)
|
||||
unrelated_project.add_maintainer(project_bot)
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
expect do
|
||||
post invitations_url(source, maintainer),
|
||||
params: { email: project_bot.email, access_level: Member::DEVELOPER }
|
||||
|
||||
expect(json_response['status']).to eq 'error'
|
||||
expect(json_response['message'][project_bot.email]).to include('User project bots cannot be added to other groups / projects')
|
||||
end.not_to change { source.members.count }
|
||||
end
|
||||
end
|
||||
|
||||
it "returns a message if member already exists" do
|
||||
post invitations_url(source, maintainer),
|
||||
params: { email: developer.email, access_level: Member::MAINTAINER }
|
||||
|
|
|
@ -395,7 +395,7 @@ RSpec.describe 'project routing' do
|
|||
# DELETE /:project_id/project_members/:id(.:format) project_members#destroy
|
||||
describe Projects::ProjectMembersController, 'routing' do
|
||||
it_behaves_like 'resource routing' do
|
||||
let(:actions) { %i[index create update destroy] }
|
||||
let(:actions) { %i[index update destroy] }
|
||||
let(:base_path) { '/gitlab/gitlabhq/-/project_members' }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable RSpec/VerifiedDoubles
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'stringio'
|
||||
require 'fileutils'
|
||||
|
||||
require_relative '../../../rubocop/formatter/todo_formatter'
|
||||
require_relative '../../../rubocop/todo_dir'
|
||||
|
||||
RSpec.describe RuboCop::Formatter::TodoFormatter do
|
||||
let(:stdout) { StringIO.new }
|
||||
let(:tmp_dir) { Dir.mktmpdir }
|
||||
let(:real_tmp_dir) { File.join(tmp_dir, 'real') }
|
||||
let(:symlink_tmp_dir) { File.join(tmp_dir, 'symlink') }
|
||||
let(:rubocop_todo_dir) { "#{symlink_tmp_dir}/.rubocop_todo" }
|
||||
let(:options) { { rubocop_todo_dir: rubocop_todo_dir } }
|
||||
let(:todo_dir) { RuboCop::TodoDir.new(rubocop_todo_dir) }
|
||||
|
||||
subject(:formatter) { described_class.new(stdout, options) }
|
||||
|
||||
around do |example|
|
||||
FileUtils.mkdir(real_tmp_dir)
|
||||
FileUtils.symlink(real_tmp_dir, symlink_tmp_dir)
|
||||
|
||||
Dir.chdir(symlink_tmp_dir) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.remove_entry(tmp_dir)
|
||||
end
|
||||
|
||||
context 'with offenses detected' do
|
||||
let(:offense) { fake_offense('A/Offense') }
|
||||
let(:offense_too_many) { fake_offense('B/TooManyOffenses') }
|
||||
let(:offense_autocorrect) { fake_offense('B/AutoCorrect') }
|
||||
|
||||
before do
|
||||
stub_const("#{described_class}::MAX_OFFENSE_COUNT", 1)
|
||||
|
||||
stub_rubocop_registry(
|
||||
'A/Offense' => { autocorrectable: false },
|
||||
'B/AutoCorrect' => { autocorrectable: true }
|
||||
)
|
||||
end
|
||||
|
||||
def run_formatter
|
||||
formatter.started(%w[a.rb b.rb c.rb d.rb])
|
||||
formatter.file_finished('c.rb', [offense_too_many])
|
||||
formatter.file_finished('a.rb', [offense_too_many, offense, offense_too_many])
|
||||
formatter.file_finished('b.rb', [])
|
||||
formatter.file_finished('d.rb', [offense_autocorrect])
|
||||
formatter.finished(%w[a.rb b.rb c.rb d.rb])
|
||||
end
|
||||
|
||||
it 'outputs its actions' do
|
||||
run_formatter
|
||||
|
||||
expect(stdout.string).to eq(<<~OUTPUT)
|
||||
Written to .rubocop_todo/a/offense.yml
|
||||
Written to .rubocop_todo/b/auto_correct.yml
|
||||
Written to .rubocop_todo/b/too_many_offenses.yml
|
||||
OUTPUT
|
||||
end
|
||||
|
||||
it 'creates YAML files', :aggregate_failures do
|
||||
run_formatter
|
||||
|
||||
expect(rubocop_todo_dir_listing).to contain_exactly(
|
||||
'a/offense.yml', 'b/auto_correct.yml', 'b/too_many_offenses.yml'
|
||||
)
|
||||
|
||||
expect(todo_yml('A/Offense')).to eq(<<~YAML)
|
||||
---
|
||||
A/Offense:
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
YAML
|
||||
|
||||
expect(todo_yml('B/AutoCorrect')).to eq(<<~YAML)
|
||||
---
|
||||
# Cop supports --auto-correct.
|
||||
B/AutoCorrect:
|
||||
Exclude:
|
||||
- 'd.rb'
|
||||
YAML
|
||||
|
||||
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
- 'c.rb'
|
||||
YAML
|
||||
end
|
||||
|
||||
context 'when cop previously not explicitly disabled' do
|
||||
before do
|
||||
todo_dir.write('B/TooManyOffenses', <<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Exclude:
|
||||
- 'x.rb'
|
||||
YAML
|
||||
end
|
||||
|
||||
it 'does not disable cop' do
|
||||
run_formatter
|
||||
|
||||
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
- 'c.rb'
|
||||
YAML
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cop previously explicitly disabled in rubocop_todo/' do
|
||||
before do
|
||||
todo_dir.write('B/TooManyOffenses', <<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'x.rb'
|
||||
YAML
|
||||
|
||||
todo_dir.inspect_all
|
||||
end
|
||||
|
||||
it 'keeps cop disabled' do
|
||||
run_formatter
|
||||
|
||||
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
# Offense count: 3
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
- 'c.rb'
|
||||
YAML
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cop previously explicitly disabled in rubocop_todo.yml' do
|
||||
before do
|
||||
File.write('.rubocop_todo.yml', <<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'x.rb'
|
||||
YAML
|
||||
end
|
||||
|
||||
it 'keeps cop disabled' do
|
||||
run_formatter
|
||||
|
||||
expect(todo_yml('B/TooManyOffenses')).to eq(<<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
# Offense count: 3
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
- 'c.rb'
|
||||
YAML
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cop configuration in both .rubocop_todo/ and .rubocop_todo.yml' do
|
||||
before do
|
||||
todo_dir.write('B/TooManyOffenses', <<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
YAML
|
||||
|
||||
todo_dir.write('A/Offense', <<~YAML)
|
||||
---
|
||||
A/Offense:
|
||||
Exclude:
|
||||
- 'a.rb'
|
||||
YAML
|
||||
|
||||
todo_dir.inspect_all
|
||||
|
||||
File.write('.rubocop_todo.yml', <<~YAML)
|
||||
---
|
||||
B/TooManyOffenses:
|
||||
Exclude:
|
||||
- 'x.rb'
|
||||
A/Offense:
|
||||
Exclude:
|
||||
- 'y.rb'
|
||||
YAML
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect { run_formatter }.to raise_error(RuntimeError, <<~TXT)
|
||||
Multiple configurations found for cops:
|
||||
- A/Offense
|
||||
- B/TooManyOffenses
|
||||
TXT
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without offenses detected' do
|
||||
before do
|
||||
formatter.started(%w[a.rb b.rb])
|
||||
formatter.file_finished('a.rb', [])
|
||||
formatter.file_finished('b.rb', [])
|
||||
formatter.finished(%w[a.rb b.rb])
|
||||
end
|
||||
|
||||
it 'does not output anything' do
|
||||
expect(stdout.string).to eq('')
|
||||
end
|
||||
|
||||
it 'does not write any YAML files' do
|
||||
expect(rubocop_todo_dir_listing).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'without files to inspect' do
|
||||
before do
|
||||
formatter.started([])
|
||||
formatter.finished([])
|
||||
end
|
||||
|
||||
it 'does not output anything' do
|
||||
expect(stdout.string).to eq('')
|
||||
end
|
||||
|
||||
it 'does not write any YAML files' do
|
||||
expect(rubocop_todo_dir_listing).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rubocop_todo_dir_listing
|
||||
Dir.glob("#{rubocop_todo_dir}/**/*")
|
||||
.select { |path| File.file?(path) }
|
||||
.map { |path| path.delete_prefix("#{rubocop_todo_dir}/") }
|
||||
end
|
||||
|
||||
def todo_yml(cop_name)
|
||||
todo_dir.read(cop_name)
|
||||
end
|
||||
|
||||
def fake_offense(cop_name)
|
||||
double(:offense, cop_name: cop_name)
|
||||
end
|
||||
|
||||
def stub_rubocop_registry(**cops)
|
||||
rubocop_registry = double(:rubocop_registry)
|
||||
|
||||
allow(RuboCop::Cop::Registry).to receive(:global).and_return(rubocop_registry)
|
||||
|
||||
allow(rubocop_registry).to receive(:find_by_cop_name)
|
||||
.with(String).and_return(nil)
|
||||
|
||||
cops.each do |cop_name, attributes|
|
||||
allow(rubocop_registry).to receive(:find_by_cop_name)
|
||||
.with(cop_name).and_return(fake_cop(**attributes))
|
||||
end
|
||||
end
|
||||
|
||||
def fake_cop(autocorrectable:)
|
||||
double(:cop, support_autocorrect?: autocorrectable)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable RSpec/VerifiedDoubles
|
|
@ -0,0 +1,218 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'fileutils'
|
||||
require 'active_support/inflector/inflections'
|
||||
|
||||
require_relative '../../rubocop/todo_dir'
|
||||
|
||||
RSpec.describe RuboCop::TodoDir do
|
||||
let(:todo_dir) { described_class.new(directory) }
|
||||
let(:directory) { Dir.mktmpdir }
|
||||
let(:cop_name) { 'RSpec/VariableInstance' }
|
||||
let(:cop_name_underscore) { ActiveSupport::Inflector.underscore(cop_name) }
|
||||
let(:yaml_path) { "#{File.join(directory, cop_name_underscore)}.yml" }
|
||||
|
||||
around do |example|
|
||||
Dir.chdir(directory) do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.remove_entry(directory)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
context 'when passing inflector' do
|
||||
let(:fake_inflector) { double(:inflector) } # rubocop:disable RSpec/VerifiedDoubles
|
||||
let(:todo_dir) { described_class.new(directory, inflector: fake_inflector) }
|
||||
|
||||
before do
|
||||
allow(fake_inflector).to receive(:underscore)
|
||||
.with(cop_name)
|
||||
.and_return(cop_name_underscore)
|
||||
end
|
||||
|
||||
it 'calls .underscore' do
|
||||
todo_dir.write(cop_name, 'a')
|
||||
|
||||
expect(fake_inflector).to have_received(:underscore)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#directory' do
|
||||
subject { todo_dir.directory }
|
||||
|
||||
it { is_expected.to eq(directory) }
|
||||
end
|
||||
|
||||
describe '#read' do
|
||||
let(:content) { 'a' }
|
||||
|
||||
subject { todo_dir.read(cop_name) }
|
||||
|
||||
context 'when file exists' do
|
||||
before do
|
||||
todo_dir.write(cop_name, content)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(content) }
|
||||
end
|
||||
|
||||
context 'when file is missing' do
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#write' do
|
||||
let(:content) { 'a' }
|
||||
|
||||
subject { todo_dir.write(cop_name, content) }
|
||||
|
||||
it { is_expected.to eq(yaml_path) }
|
||||
|
||||
it 'writes content to YAML file' do
|
||||
subject
|
||||
|
||||
expect(File.read(yaml_path)).to eq(content)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inspect' do
|
||||
subject { todo_dir.inspect(cop_name) }
|
||||
|
||||
context 'with existing YAML file' do
|
||||
before do
|
||||
todo_dir.write(cop_name, 'a')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
|
||||
it 'moves YAML file to .inspect' do
|
||||
subject
|
||||
|
||||
expect(File).not_to exist(yaml_path)
|
||||
expect(File).to exist("#{yaml_path}.inspect")
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing YAML file' do
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#inspect_all' do
|
||||
subject { todo_dir.inspect_all }
|
||||
|
||||
context 'with YAML files' do
|
||||
before do
|
||||
todo_dir.write(cop_name, 'a')
|
||||
todo_dir.write('Other/Rule', 'a')
|
||||
todo_dir.write('Very/Nested/Rule', 'a')
|
||||
end
|
||||
|
||||
it { is_expected.to eq(3) }
|
||||
|
||||
it 'moves all YAML files to .inspect' do
|
||||
subject
|
||||
|
||||
expect(Dir.glob('**/*.yml')).to be_empty
|
||||
expect(Dir.glob('**/*.yml.inspect').size).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-YAML files' do
|
||||
before do
|
||||
File.write('file', 'a')
|
||||
File.write('file.txt', 'a')
|
||||
File.write('file.yaml', 'a') # not .yml
|
||||
end
|
||||
|
||||
it { is_expected.to eq(0) }
|
||||
|
||||
it 'does not move non-YAML files' do
|
||||
subject
|
||||
|
||||
expect(Dir.glob('**/*'))
|
||||
.to contain_exactly('file', 'file.txt', 'file.yaml')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without files' do
|
||||
it { is_expected.to eq(0) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#list_inspect' do
|
||||
let(:content) { 'a' }
|
||||
|
||||
subject { todo_dir.list_inspect }
|
||||
|
||||
context 'when file exists and is being inspected' do
|
||||
before do
|
||||
todo_dir.write(cop_name, content)
|
||||
todo_dir.inspect_all
|
||||
end
|
||||
|
||||
it do
|
||||
is_expected.to contain_exactly("#{yaml_path}.inspect")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when file exists but not being inspected' do
|
||||
before do
|
||||
todo_dir.write(cop_name, content)
|
||||
end
|
||||
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
|
||||
context 'when file is missing' do
|
||||
it { is_expected.to be_empty }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#delete_inspected' do
|
||||
subject { todo_dir.delete_inspected }
|
||||
|
||||
context 'with YAML files' do
|
||||
before do
|
||||
todo_dir.write(cop_name, 'a')
|
||||
todo_dir.write('Other/Rule', 'a')
|
||||
todo_dir.write('Very/Nested/Rule', 'a')
|
||||
todo_dir.inspect_all
|
||||
end
|
||||
|
||||
it { is_expected.to eq(3) }
|
||||
|
||||
it 'deletes all .inspected YAML files' do
|
||||
subject
|
||||
|
||||
expect(Dir.glob('**/*.yml.inspect')).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-YAML files' do
|
||||
before do
|
||||
File.write('file.inspect', 'a')
|
||||
File.write('file.txt.inspect', 'a')
|
||||
File.write('file.yaml.inspect', 'a') # not .yml
|
||||
end
|
||||
|
||||
it { is_expected.to eq(0) }
|
||||
|
||||
it 'does not delete non-YAML files' do
|
||||
subject
|
||||
|
||||
expect(Dir.glob('**/*')).to contain_exactly(
|
||||
'file.inspect', 'file.txt.inspect', 'file.yaml.inspect')
|
||||
end
|
||||
end
|
||||
|
||||
context 'without files' do
|
||||
it { is_expected.to eq(0) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -458,11 +458,6 @@ RSpec.configure do |config|
|
|||
end
|
||||
end
|
||||
|
||||
# Allows stdout to be redirected to reduce noise
|
||||
config.before(:each, :silence_stdout) do
|
||||
$stdout = StringIO.new
|
||||
end
|
||||
|
||||
# Makes diffs show entire non-truncated values.
|
||||
config.before(:each, unlimited_max_formatted_output_length: true) do |_example|
|
||||
config.expect_with :rspec do |c|
|
||||
|
@ -475,10 +470,6 @@ RSpec.configure do |config|
|
|||
allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
|
||||
end
|
||||
|
||||
config.after(:each, :silence_stdout) do
|
||||
$stdout = STDOUT
|
||||
end
|
||||
|
||||
config.disable_monkey_patching!
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Allows stdout to be redirected to reduce noise
|
||||
config.before(:each, :silence_stdout) do
|
||||
$stdout = StringIO.new
|
||||
end
|
||||
|
||||
config.after(:each, :silence_stdout) do
|
||||
$stdout = STDOUT
|
||||
end
|
||||
end
|
|
@ -0,0 +1,168 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable RSpec/VerifiedDoubles
|
||||
|
||||
require 'fast_spec_helper'
|
||||
require 'rake'
|
||||
require 'fileutils'
|
||||
|
||||
require_relative '../support/silence_stdout'
|
||||
require_relative '../support/helpers/next_instance_of'
|
||||
require_relative '../support/helpers/rake_helpers'
|
||||
require_relative '../../rubocop/todo_dir'
|
||||
|
||||
RSpec.describe 'rubocop rake tasks', :silence_stdout do
|
||||
include RakeHelpers
|
||||
|
||||
before do
|
||||
stub_const('Rails', double(:rails_env))
|
||||
allow(Rails).to receive(:env).and_return(double(production?: false))
|
||||
|
||||
stub_const('ENV', ENV.to_hash.dup)
|
||||
|
||||
Rake.application.rake_require 'tasks/rubocop'
|
||||
end
|
||||
|
||||
describe 'todo:generate', :aggregate_failures do
|
||||
let(:tmp_dir) { Dir.mktmpdir }
|
||||
let(:rubocop_todo_dir) { File.join(tmp_dir, '.rubocop_todo') }
|
||||
let(:todo_dir) { RuboCop::TodoDir.new(rubocop_todo_dir) }
|
||||
|
||||
around do |example|
|
||||
Dir.chdir(tmp_dir) do
|
||||
with_inflections do
|
||||
example.run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
allow(RuboCop::TodoDir).to receive(:new).and_return(todo_dir)
|
||||
|
||||
# This Ruby file will trigger the following 3 offenses.
|
||||
File.write('a.rb', <<~RUBY)
|
||||
a+b
|
||||
|
||||
RUBY
|
||||
|
||||
# Mimic GitLab's .rubocop_todo.yml avoids relying on RuboCop's
|
||||
# default.yml configuration.
|
||||
File.write('.rubocop.yml', <<~YAML)
|
||||
<% unless ENV['REVEAL_RUBOCOP_TODO'] == '1' %>
|
||||
<% Dir.glob('.rubocop_todo/**/*.yml').each do |rubocop_todo_yaml| %>
|
||||
- '<%= rubocop_todo_yaml %>'
|
||||
<% end %>
|
||||
- '.rubocop_todo.yml'
|
||||
<% end %>
|
||||
|
||||
AllCops:
|
||||
NewCops: enable # Avoiding RuboCop warnings
|
||||
|
||||
Layout/SpaceAroundOperators:
|
||||
Enabled: true
|
||||
|
||||
Layout/TrailingEmptyLines:
|
||||
Enabled: true
|
||||
|
||||
Lint/Syntax:
|
||||
Enabled: true
|
||||
|
||||
Style/FrozenStringLiteralComment:
|
||||
Enabled: true
|
||||
YAML
|
||||
|
||||
# Required to verify that we are revealing all TODOs via
|
||||
# ENV['REVEAL_RUBOCOP_TODO'] = '1'.
|
||||
# This file can be removed from specs after we've moved all offenses from
|
||||
# .rubocop_todo.yml to .rubocop_todo/**/*.yml.
|
||||
File.write('.rubocop_todo.yml', <<~YAML)
|
||||
# Too many offenses
|
||||
Layout/SpaceAroundOperators:
|
||||
Enabled: false
|
||||
YAML
|
||||
|
||||
# Previous offense now fixed.
|
||||
todo_dir.write('Lint/Syntax', '')
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.remove_entry(tmp_dir)
|
||||
end
|
||||
|
||||
context 'without arguments' do
|
||||
let(:run_task) { run_rake_task('rubocop:todo:generate') }
|
||||
|
||||
it 'generates TODOs for all RuboCop rules' do
|
||||
expect { run_task }.to output(<<~OUTPUT).to_stdout
|
||||
Generating RuboCop TODOs with:
|
||||
rubocop --parallel --format RuboCop::Formatter::TodoFormatter
|
||||
|
||||
This might take a while...
|
||||
Written to .rubocop_todo/layout/space_around_operators.yml
|
||||
Written to .rubocop_todo/layout/trailing_empty_lines.yml
|
||||
Written to .rubocop_todo/style/frozen_string_literal_comment.yml
|
||||
OUTPUT
|
||||
|
||||
expect(rubocop_todo_dir_listing).to contain_exactly(
|
||||
'layout/space_around_operators.yml',
|
||||
'layout/trailing_empty_lines.yml',
|
||||
'style/frozen_string_literal_comment.yml'
|
||||
)
|
||||
end
|
||||
|
||||
it 'sets acronyms for inflections' do
|
||||
run_task
|
||||
|
||||
expect(ActiveSupport::Inflector.inflections.acronyms).to include(
|
||||
'rspec' => 'RSpec',
|
||||
'graphql' => 'GraphQL'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cop names as arguments' do
|
||||
let(:run_task) do
|
||||
cop_names = %w[
|
||||
Style/FrozenStringLiteralComment Layout/TrailingEmptyLines
|
||||
Lint/Syntax
|
||||
]
|
||||
|
||||
run_rake_task('rubocop:todo:generate', cop_names)
|
||||
end
|
||||
|
||||
it 'generates TODOs for given RuboCop cops' do
|
||||
expect { run_task }.to output(<<~OUTPUT).to_stdout
|
||||
Generating RuboCop TODOs with:
|
||||
rubocop --parallel --format RuboCop::Formatter::TodoFormatter --only Layout/TrailingEmptyLines,Lint/Syntax,Style/FrozenStringLiteralComment
|
||||
|
||||
This might take a while...
|
||||
Written to .rubocop_todo/layout/trailing_empty_lines.yml
|
||||
Written to .rubocop_todo/style/frozen_string_literal_comment.yml
|
||||
OUTPUT
|
||||
|
||||
expect(rubocop_todo_dir_listing).to contain_exactly(
|
||||
'layout/trailing_empty_lines.yml',
|
||||
'style/frozen_string_literal_comment.yml'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rubocop_todo_dir_listing
|
||||
Dir.glob("#{rubocop_todo_dir}/**/*")
|
||||
.select { |path| File.file?(path) }
|
||||
.map { |path| path.delete_prefix("#{rubocop_todo_dir}/") }
|
||||
end
|
||||
|
||||
def with_inflections
|
||||
original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
|
||||
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
|
||||
|
||||
yield
|
||||
ensure
|
||||
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable RSpec/VerifiedDoubles
|
Loading…
Reference in New Issue