Commit 8c5f2b7f authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 8c79ba4e 288c7965
......@@ -644,3 +644,16 @@ Cop/UserAdmin:
Performance/OpenStruct:
Exclude:
- 'ee/spec/**/*.rb'
# See https://gitlab.com/gitlab-org/gitlab/-/issues/327495
Style/RegexpLiteral:
Enabled: false
Style/RegexpLiteralMixedPreserve:
Enabled: true
SupportedStyles:
- slashes
- percent_r
- mixed
- mixed_preserve
EnforcedStyle: mixed_preserve
......@@ -3330,3 +3330,60 @@ Gitlab/FeatureAvailableUsage:
- 'ee/spec/models/project_spec.rb'
- 'lib/api/helpers/related_resources_helpers.rb'
- 'spec/models/concerns/featurable_spec.rb'
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/327490
Style/RegexpLiteralMixedPreserve:
Exclude:
- 'app/controllers/projects/repositories_controller.rb'
- 'app/helpers/ci/variables_helper.rb'
- 'app/models/alert_management/alert.rb'
- 'app/models/application_setting.rb'
- 'app/models/blob_viewer/go_mod.rb'
- 'app/models/concerns/ci/maskable.rb'
- 'app/models/operations/feature_flag.rb'
- 'app/models/packages/go/module.rb'
- 'app/models/project_services/chat_message/base_message.rb'
- 'app/services/packages/conan/search_service.rb'
- 'app/services/projects/update_remote_mirror_service.rb'
- 'config/initializers/rspec_profiling.rb'
- 'ee/app/models/status_page/project_setting.rb'
- 'ee/app/presenters/vulnerability_presenter.rb'
- 'ee/lib/api/geo_nodes.rb'
- 'ee/lib/gitlab/vulnerabilities/standard_vulnerability.rb'
- 'ee/spec/controllers/concerns/ee/routable_actions/sso_enforcement_redirect_spec.rb'
- 'ee/spec/controllers/concerns/routable_actions_spec.rb'
- 'ee/spec/controllers/groups/groups_controller_spec.rb'
- 'ee/spec/features/groups/saml_enforcement_spec.rb'
- 'ee/spec/features/markdown/metrics_spec.rb'
- 'ee/spec/lib/gitlab/database/load_balancing/load_balancer_spec.rb'
- 'ee/spec/models/project_services/jira_service_spec.rb'
- 'ee/spec/services/jira/requests/issues/list_service_spec.rb'
- 'lib/api/invitations.rb'
- 'lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb'
- 'lib/gitlab/metrics/requests_rack_middleware.rb'
- 'lib/gitlab/metrics/subscribers/active_record.rb'
- 'lib/gitlab/regex.rb'
- 'lib/gitlab/utils.rb'
- 'lib/product_analytics/tracker.rb'
- 'qa/qa/page/project/settings/advanced.rb'
- 'qa/spec/service/docker_run/gitlab_runner_spec.rb'
- 'rubocop/cop/gitlab/duplicate_spec_location.rb'
- 'spec/features/clusters/cluster_health_dashboard_spec.rb'
- 'spec/features/markdown/metrics_spec.rb'
- 'spec/features/search/user_searches_for_code_spec.rb'
- 'spec/features/snippets/embedded_snippet_spec.rb'
- 'spec/helpers/diff_helper_spec.rb'
- 'spec/helpers/releases_helper_spec.rb'
- 'spec/lib/gitlab/ci/reports/test_case_spec.rb'
- 'spec/lib/gitlab/consul/internal_spec.rb'
- 'spec/lib/gitlab/import_export/shared_spec.rb'
- 'spec/lib/gitlab/utils/usage_data_spec.rb'
- 'spec/presenters/ci/build_runner_presenter_spec.rb'
- 'spec/requests/api/projects_spec.rb'
- 'spec/services/jira/requests/projects/list_service_spec.rb'
- 'spec/support/capybara.rb'
- 'spec/support/helpers/grafana_api_helpers.rb'
- 'spec/support/helpers/query_recorder.rb'
- 'spec/support/helpers/require_migration.rb'
- 'spec/support/shared_examples/models/slack_mattermost_notifications_shared_examples.rb'
- 'spec/views/layouts/_head.html.haml_spec.rb'
......@@ -922,13 +922,6 @@ Style/RedundantRegexpEscape:
Style/RedundantSelf:
Enabled: false
# Offense count: 213
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, AllowInnerSlashes.
# SupportedStyles: slashes, percent_r, mixed
Style/RegexpLiteral:
Enabled: false
# Offense count: 53
# Cop supports --auto-correct.
Style/RescueModifier:
......
......@@ -12,6 +12,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
memberId: {
type: Number,
......@@ -19,7 +20,11 @@ export default {
},
},
computed: {
...mapState(['memberPath']),
...mapState({
memberPath(state) {
return state[this.namespace].memberPath;
},
}),
approvePath() {
return this.memberPath.replace(/:id$/, `${this.memberId}/approve_access_request`);
},
......
......@@ -12,6 +12,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
groupLink: {
type: Object,
......@@ -19,7 +20,11 @@ export default {
},
},
methods: {
...mapActions(['showRemoveGroupLinkModal']),
...mapActions({
showRemoveGroupLinkModal(dispatch, payload) {
return dispatch(`${this.namespace}/showRemoveGroupLinkModal`, payload);
},
}),
},
};
</script>
......
......@@ -8,6 +8,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
memberId: {
type: Number,
......@@ -43,7 +44,11 @@ export default {
},
},
computed: {
...mapState(['memberPath']),
...mapState({
memberPath(state) {
return state[this.namespace].memberPath;
},
}),
computedMemberPath() {
return this.memberPath.replace(':id', this.memberId);
},
......
......@@ -12,6 +12,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
memberId: {
type: Number,
......@@ -19,7 +20,11 @@ export default {
},
},
computed: {
...mapState(['memberPath']),
...mapState({
memberPath(state) {
return state[this.namespace].memberPath;
},
}),
resendPath() {
return this.memberPath.replace(/:id$/, `${this.memberId}/resend_invite`);
},
......
......@@ -9,8 +9,16 @@ import MembersTable from './table/members_table.vue';
export default {
name: 'MembersApp',
components: { MembersTable, FilterSortContainer, GlAlert },
inject: ['namespace'],
computed: {
...mapState(['showError', 'errorMessage']),
...mapState({
showError(state) {
return state[this.namespace].showError;
},
errorMessage(state) {
return state[this.namespace].errorMessage;
},
}),
},
watch: {
showError(value) {
......@@ -23,7 +31,9 @@ export default {
},
methods: {
...mapMutations({
hideError: HIDE_ERROR,
hideError(commit) {
return commit(`${this.namespace}/${HIDE_ERROR}`);
},
}),
},
};
......
......@@ -6,8 +6,16 @@ import SortDropdown from './sort_dropdown.vue';
export default {
name: 'FilterSortContainer',
components: { MembersFilteredSearchBar, SortDropdown },
inject: ['namespace'],
computed: {
...mapState(['filteredSearchBar', 'tableSortableFields']),
...mapState({
filteredSearchBar(state) {
return state[this.namespace].filteredSearchBar;
},
tableSortableFields(state) {
return state[this.namespace].tableSortableFields;
},
}),
showContainer() {
return this.filteredSearchBar.show || this.showSortDropdown;
},
......
......@@ -37,14 +37,18 @@ export default {
],
},
],
inject: ['sourceId', 'canManageMembers'],
inject: ['namespace', 'sourceId', 'canManageMembers'],
data() {
return {
initialFilterValue: [],
};
},
computed: {
...mapState(['filteredSearchBar']),
...mapState({
filteredSearchBar(state) {
return state[this.namespace].filteredSearchBar;
},
}),
tokens() {
return this.$options.availableTokens.filter((token) => {
if (
......
......@@ -8,8 +8,16 @@ import { parseSortParam, buildSortHref } from '~/members/utils';
export default {
name: 'SortDropdown',
components: { GlSorting, GlSortingItem },
inject: ['namespace'],
computed: {
...mapState(['tableSortableFields', 'filteredSearchBar']),
...mapState({
tableSortableFields(state) {
return state[this.namespace].tableSortableFields;
},
filteredSearchBar(state) {
return state[this.namespace].filteredSearchBar;
},
}),
sort() {
return parseSortParam(this.tableSortableFields);
},
......
......@@ -23,6 +23,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
member: {
type: Object,
......@@ -30,7 +31,11 @@ export default {
},
},
computed: {
...mapState(['memberPath']),
...mapState({
memberPath(state) {
return state[this.namespace].memberPath;
},
}),
leavePath() {
return this.memberPath.replace(/:id$/, 'leave');
},
......
......@@ -22,8 +22,19 @@ export default {
},
modalId: REMOVE_GROUP_LINK_MODAL_ID,
components: { GlModal, GlSprintf, GlForm },
inject: ['namespace'],
computed: {
...mapState(['memberPath', 'groupLinkToRemove', 'removeGroupLinkModalVisible']),
...mapState({
memberPath(state) {
return state[this.namespace].memberPath;
},
groupLinkToRemove(state) {
return state[this.namespace].groupLinkToRemove;
},
removeGroupLinkModalVisible(state) {
return state[this.namespace].removeGroupLinkModalVisible;
},
}),
groupLinkPath() {
return this.memberPath.replace(/:id$/, this.groupLinkToRemove?.id);
},
......@@ -35,7 +46,11 @@ export default {
},
},
methods: {
...mapActions(['hideRemoveGroupLinkModal']),
...mapActions({
hideRemoveGroupLinkModal(dispatch) {
return dispatch(`${this.namespace}/hideRemoveGroupLinkModal`);
},
}),
handlePrimary() {
this.$refs.form.$el.submit();
},
......
......@@ -7,6 +7,7 @@ import { s__ } from '~/locale';
export default {
name: 'ExpirationDatepicker',
components: { GlDatepicker },
inject: ['namespace'],
props: {
member: {
type: Object,
......@@ -46,7 +47,11 @@ export default {
}
},
methods: {
...mapActions(['updateMemberExpiration']),
...mapActions({
updateMemberExpiration(dispatch, payload) {
return dispatch(`${this.namespace}/updateMemberExpiration`, payload);
},
}),
handleInput(date) {
this.busy = true;
this.updateMemberExpiration({
......
......@@ -31,9 +31,19 @@ export default {
LdapOverrideConfirmationModal: () =>
import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'),
},
inject: ['currentUserId'],
inject: ['namespace', 'currentUserId'],
computed: {
...mapState(['members', 'tableFields', 'tableAttrs']),
...mapState({
members(state) {
return state[this.namespace].members;
},
tableFields(state) {
return state[this.namespace].tableFields;
},
tableAttrs(state) {
return state[this.namespace].tableAttrs;
},
}),
filteredFields() {
return FIELDS.filter(
(field) => this.tableFields.includes(field.key) && this.showField(field),
......
......@@ -11,6 +11,7 @@ export default {
GlDropdownItem,
LdapDropdownItem: () => import('ee_component/members/components/ldap/ldap_dropdown_item.vue'),
},
inject: ['namespace'],
props: {
member: {
type: Object,
......@@ -44,7 +45,11 @@ export default {
}
},
methods: {
...mapActions(['updateMemberRole']),
...mapActions({
updateMemberRole(dispatch, payload) {
return dispatch(`${this.namespace}/updateMemberRole`, payload);
},
}),
handleSelect(value, name) {
if (value === this.member.accessLevel.integerValue) {
return;
......
......@@ -8,6 +8,7 @@ import membersStore from './store';
export const initMembersApp = (
el,
{
namespace,
tableFields = [],
tableAttrs = {},
tableSortableFields = [],
......@@ -24,22 +25,25 @@ export const initMembersApp = (
const { sourceId, canManageMembers, ...vuexStoreAttributes } = parseDataAttributes(el);
const store = new Vuex.Store(
membersStore({
...vuexStoreAttributes,
tableFields,
tableAttrs,
tableSortableFields,
requestFormatter,
filteredSearchBar,
}),
);
const store = new Vuex.Store({
modules: {
[namespace]: membersStore({
...vuexStoreAttributes,
tableFields,
tableAttrs,
tableSortableFields,
requestFormatter,
filteredSearchBar,
}),
},
});
return new Vue({
el,
components: { App },
store,
provide: {
namespace,
currentUserId: gon.current_user_id || null,
sourceId,
canManageMembers,
......
......@@ -3,6 +3,7 @@ import mutations from 'ee_else_ce/members/store/mutations';
import createState from 'ee_else_ce/members/store/state';
export default (initialState) => ({
namespaced: true,
state: createState(initialState),
actions,
mutations,
......
......@@ -8,6 +8,7 @@ import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigg
import { s__ } from '~/locale';
import memberExpirationDate from '~/member_expiration_date';
import { initMembersApp } from '~/members';
import { MEMBER_TYPES } from '~/members/constants';
import { groupLinkRequestFormatter } from '~/members/utils';
import UsersSelect from '~/users_select';
import RemoveMemberModal from '~/vue_shared/components/remove_member_modal.vue';
......@@ -29,6 +30,7 @@ function mountRemoveMemberModal() {
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-group-members-list'), {
namespace: MEMBER_TYPES.user,
tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
......@@ -43,6 +45,7 @@ initMembersApp(document.querySelector('.js-group-members-list'), {
});
initMembersApp(document.querySelector('.js-group-group-links-list'), {
namespace: MEMBER_TYPES.group,
tableFields: SHARED_FIELDS.concat('granted'),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
......@@ -51,6 +54,7 @@ initMembersApp(document.querySelector('.js-group-group-links-list'), {
requestFormatter: groupLinkRequestFormatter,
});
initMembersApp(document.querySelector('.js-group-invited-members-list'), {
namespace: MEMBER_TYPES.invite,
tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: groupMemberRequestFormatter,
filteredSearchBar: {
......@@ -62,6 +66,7 @@ initMembersApp(document.querySelector('.js-group-invited-members-list'), {
},
});
initMembersApp(document.querySelector('.js-group-access-requests-list'), {
namespace: MEMBER_TYPES.accessRequest,
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: groupMemberRequestFormatter,
});
......
......@@ -7,6 +7,7 @@ import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigg
import { s__ } from '~/locale';
import memberExpirationDate from '~/member_expiration_date';
import { initMembersApp } from '~/members';
import { MEMBER_TYPES } from '~/members/constants';
import { groupLinkRequestFormatter } from '~/members/utils';
import { projectMemberRequestFormatter } from '~/projects/members/utils';
import UsersSelect from '~/users_select';
......@@ -42,6 +43,7 @@ new UsersSelect(); // eslint-disable-line no-new
const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-project-members-list'), {
namespace: MEMBER_TYPES.user,
tableFields: SHARED_FIELDS.concat(['source', 'granted']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: ['account', 'granted', 'maxRole', 'lastSignIn'],
......@@ -56,6 +58,7 @@ initMembersApp(document.querySelector('.js-project-members-list'), {
});
initMembersApp(document.querySelector('.js-project-group-links-list'), {
namespace: MEMBER_TYPES.group,
tableFields: SHARED_FIELDS.concat('granted'),
tableAttrs: {
table: { 'data-qa-selector': 'groups_list' },
......@@ -72,11 +75,13 @@ initMembersApp(document.querySelector('.js-project-group-links-list'), {
});
initMembersApp(document.querySelector('.js-project-invited-members-list'), {
namespace: MEMBER_TYPES.invite,
tableFields: SHARED_FIELDS.concat('invited'),
requestFormatter: projectMemberRequestFormatter,
});
initMembersApp(document.querySelector('.js-project-access-requests-list'), {
namespace: MEMBER_TYPES.accessRequest,
tableFields: SHARED_FIELDS.concat('requested'),
requestFormatter: projectMemberRequestFormatter,
});
......@@ -15,4 +15,7 @@ export const STAGE_VIEW = 'stage';
export const LAYER_VIEW = 'layer';
export const VIEW_TYPE_KEY = 'pipeline_graph_view_type';
export const SINGLE_JOB = 'single_job';
export const JOB_DROPDOWN = 'job_dropdown';
export const IID_FAILURE = 'missing_iid';
<script>
import { reportToSentry } from '../../utils';
import { JOB_DROPDOWN, SINGLE_JOB } from './constants';
import JobItem from './job_item.vue';
/**
......@@ -28,6 +29,10 @@ export default {
default: '',
},
},
jobItemTypes: {
jobDropdown: JOB_DROPDOWN,
singleJob: SINGLE_JOB,
},
computed: {
computedJobId() {
return this.pipelineId > -1 ? `${this.group.name}-${this.pipelineId}` : '';
......@@ -57,11 +62,10 @@ export default {
>
<div class="gl-display-flex gl-align-items-center gl-justify-content-space-between">
<job-item
:dropdown-length="group.size"
:type="$options.jobItemTypes.jobDropdown"
:group-tooltip="tooltipText"
:job="group"
:stage-name="stageName"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
<div class="gl-font-weight-100 gl-font-size-lg gl-ml-n4">{{ group.size }}</div>
......@@ -75,6 +79,7 @@ export default {
<job-item
:dropdown-length="group.size"
:job="job"
:type="$options.jobItemTypes.singleJob"
css-class-job-name="mini-pipeline-graph-dropdown-item"
@pipelineActionRequestComplete="pipelineActionRequestComplete"
/>
......
......@@ -8,7 +8,7 @@ import { reportToSentry } from '../../utils';
import ActionComponent from '../jobs_shared/action_component.vue';
import JobNameComponent from '../jobs_shared/job_name_component.vue';
import { accessValue } from './accessors';
import { REST } from './constants';
import { REST, SINGLE_JOB } from './constants';
/**
* Renders the badge for the pipeline graph and the job's dropdown.
......@@ -97,6 +97,11 @@ export default {
required: false,
default: '',
},
type: {
type: String,
required: false,
default: SINGLE_JOB,
},
},
computed: {
boundary() {
......@@ -111,6 +116,9 @@ export default {
hasDetails() {
return accessValue(this.dataMethod, 'hasDetails', this.status);
},
isSingleItem() {
return this.type === SINGLE_JOB;
},
nameComponent() {
return this.hasDetails ? 'gl-link' : 'div';
},
......@@ -177,6 +185,17 @@ export default {
hideTooltips() {
this.$root.$emit(BV_HIDE_TOOLTIP);
},
jobItemClick(evt) {
if (this.isSingleItem) {
/*
This is so the jobDropdown still toggles. Issue to refactor:
https://gitlab.com/gitlab-org/gitlab/-/issues/267117
*/
evt.stopPropagation();
}
this.hideTooltips();
},
pipelineActionRequestComplete() {
this.$emit('pipelineActionRequestComplete');
},
......@@ -201,7 +220,7 @@ export default {
:href="detailsPath"
class="js-pipeline-graph-job-link qa-job-link menu-item gl-text-gray-900 gl-active-text-decoration-none gl-focus-text-decoration-none gl-hover-text-decoration-none gl-w-full"
:data-testid="testId"
@click.stop="hideTooltips"
@click="jobItemClick"
@mouseout="hideTooltips"
>
<div class="ci-job-name-component gl-display-flex gl-align-items-center">
......
- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
- current_route_path = request.fullpath.match(%r{-/tree/[^/]+/(.+$)}).to_a[1]
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
- @content_class = "limit-container-width" unless fluid_layout
- @skip_current_level_breadcrumb = true
......
- current_route_path = request.fullpath.match(/-\/tree\/[^\/]+\/(.+$)/).to_a[1]
- current_route_path = request.fullpath.match(%r{-/tree/[^/]+/(.+$)}).to_a[1]
- add_page_startup_graphql_call('repository/path_last_commit', { projectPath: @project.full_path, ref: current_ref, path: current_route_path || "" })
- add_page_startup_graphql_call('repository/permissions', { projectPath: @project.full_path })
- add_page_startup_graphql_call('repository/files', { nextPageCursor: "", pageSize: 100, projectPath: @project.full_path, ref: current_ref, path: current_route_path || "/"})
......
---
title: Add a migration to insert trail plans within SAAS for Ultimate and Premium plans
merge_request: 57814
author:
type: added
# frozen_string_literal: true
class AddNewTrailPlans < ActiveRecord::Migration[6.0]
class Plan < ActiveRecord::Base
self.inheritance_column = :_type_disabled
has_one :limits, class_name: 'PlanLimits'
def actual_limits
self.limits || self.build_limits
end
end
class PlanLimits < ActiveRecord::Base
self.inheritance_column = :_type_disabled
belongs_to :plan
end
def create_plan_limits(plan_limit_name, plan)
plan_limit = Plan.find_or_initialize_by(name: plan_limit_name).actual_limits.dup
plan_limit.plan = plan
plan_limit.save!
end
def up
return unless Gitlab.dev_env_or_com?
ultimate_trial = Plan.create!(name: 'ultimate_trial', title: 'Ultimate Trial')
premium_trial = Plan.create!(name: 'premium_trial', title: 'Premium Trial')
create_plan_limits('gold', ultimate_trial)
create_plan_limits('silver', premium_trial)
end
def down
return unless Gitlab.dev_env_or_com?
Plan.where(name: %w(ultimate_trial premium_trial)).delete_all
end
end
b40c702ea6b2120da6fe11b213064a7a124dbc86bfb2d6785bfd2274c44f1e22
\ No newline at end of file
......@@ -6,6 +6,7 @@ import { s__ } from '~/locale';
export default {
name: 'LdapDropdownItem',
components: { GlDropdownItem, GlDropdownDivider },
inject: ['namespace'],
props: {
memberId: {
type: Number,
......@@ -13,7 +14,11 @@ export default {
},
},
methods: {
...mapActions(['updateLdapOverride']),
...mapActions({
updateLdapOverride(dispatch, payload) {
return dispatch(`${this.namespace}/updateLdapOverride`, payload);
},
}),
handleClick() {
this.updateLdapOverride({ memberId: this.memberId, override: false })
.then(() => {
......
......@@ -12,6 +12,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['namespace'],
props: {
member: {
type: Object,
......@@ -19,7 +20,11 @@ export default {
},
},
methods: {
...mapActions(['showLdapOverrideConfirmationModal']),
...mapActions({
showLdapOverrideConfirmationModal(dispatch, payload) {
return dispatch(`${this.namespace}/showLdapOverrideConfirmationModal`, payload);
},
}),
},
};
</script>
......
......@@ -18,13 +18,21 @@ export default {
},
modalId: LDAP_OVERRIDE_CONFIRMATION_MODAL_ID,
components: { GlModal, GlSprintf },
inject: ['namespace'],
data() {
return {
busy: false,
};
},
computed: {
...mapState(['memberToOverride', 'ldapOverrideConfirmationModalVisible']),
...mapState({
memberToOverride(state) {
return state[this.namespace].memberToOverride;
},
ldapOverrideConfirmationModalVisible(state) {
return state[this.namespace].ldapOverrideConfirmationModalVisible;
},
}),
actionPrimary() {
return {
text: this.$options.i18n.editPermissions,
......@@ -36,7 +44,14 @@ export default {
},
},
methods: {
...mapActions(['updateLdapOverride', 'hideLdapOverrideConfirmationModal']),
...mapActions({
updateLdapOverride(dispatch, payload) {
return dispatch(`${this.namespace}/updateLdapOverride`, payload);
},
hideLdapOverrideConfirmationModal(dispatch) {
return dispatch(`${this.namespace}/hideLdapOverrideConfirmationModal`);
},
}),
handlePrimary() {
this.busy = true;
......
......@@ -3,12 +3,16 @@ import LdapOverrideButton from 'ee/members/components/ldap/ldap_override_button.
import waitForPromises from 'helpers/wait_for_promises';
import { member } from 'jest/members/mock_data';
import UserActionButtons from '~/members/components/action_buttons/user_action_buttons.vue';
import { MEMBER_TYPES } from '~/members/constants';
describe('UserActionButtons', () => {
let wrapper;
const createComponent = (propsData = {}) => {
wrapper = shallowMount(UserActionButtons, {
provide: {
namespace: MEMBER_TYPES.user,
},
propsData: {
member,
isCurrentUser: false,
......
......@@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import LdapDropdownItem from 'ee/members/components/ldap/ldap_dropdown_item.vue';
import waitForPromises from 'helpers/wait_for_promises';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -19,7 +20,14 @@ describe('LdapDropdownItem', () => {
updateLdapOverride: jest.fn(() => Promise.resolve()),
};
return new Vuex.Store({ actions });
return new Vuex.Store({
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
actions,
},
},
});
};
const createComponent = (propsData = {}) => {
......@@ -30,6 +38,9 @@ describe('LdapDropdownItem', () => {
},
localVue,
store: createStore(),
provide: {
namespace: MEMBER_TYPES.user,
},
mocks: {
$toast,
},
......
......@@ -4,6 +4,7 @@ import Vuex from 'vuex';
import LdapOverrideButton from 'ee/members/components/ldap/ldap_override_button.vue';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { member } from 'jest/members/mock_data';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -17,7 +18,14 @@ describe('LdapOverrideButton', () => {
showLdapOverrideConfirmationModal: jest.fn(),
};
return new Vuex.Store({ actions });
return new Vuex.Store({
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
actions,
},
},
});
};
const createComponent = (propsData = {}) => {
......@@ -28,6 +36,9 @@ describe('LdapOverrideButton', () => {
...propsData,
},
store: createStore(),
provide: {
namespace: MEMBER_TYPES.user,
},
directives: {
GlTooltip: createMockDirective(),
},
......
......@@ -7,6 +7,7 @@ import LdapOverrideConfirmationModal from 'ee/members/components/ldap/ldap_overr
import { LDAP_OVERRIDE_CONFIRMATION_MODAL_ID } from 'ee/members/constants';
import waitForPromises from 'helpers/wait_for_promises';
import { member } from 'jest/members/mock_data';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -31,12 +32,17 @@ describe('LdapOverrideConfirmationModal', () => {
};
return new Vuex.Store({
state: {
memberToOverride: member,
ldapOverrideConfirmationModalVisible: true,
...state,
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
memberToOverride: member,
ldapOverrideConfirmationModalVisible: true,
...state,
},
actions,
},
},
actions,
});
};
......@@ -44,6 +50,9 @@ describe('LdapOverrideConfirmationModal', () => {
wrapper = mount(LdapOverrideConfirmationModal, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.user,
},
attrs: {
static: true,
},
......
......@@ -2,12 +2,16 @@ import { GlDatepicker } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { member } from 'jest/members/mock_data';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
import { MEMBER_TYPES } from '~/members/constants';
describe('ExpirationDatepicker', () => {
let wrapper;
const createComponent = (propsData = {}) => {
wrapper = mount(ExpirationDatepicker, {
provide: {
namespace: MEMBER_TYPES.user,
},
propsData,
});
};
......
......@@ -3,6 +3,7 @@ import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import Vuex from 'vuex';
import { member as memberMock, directMember, members } from 'jest/members/mock_data';
import MembersTable from '~/members/components/table/members_table.vue';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -12,14 +13,19 @@ describe('MemberList', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
members: [],
tableFields: [],
tableAttrs: {
table: { 'data-qa-selector': 'members_list' },
tr: { 'data-qa-selector': 'member_row' },
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
members: [],
tableFields: [],
tableAttrs: {
table: { 'data-qa-selector': 'members_list' },
tr: { 'data-qa-selector': 'member_row' },
},
...state,
},
},
...state,
},
});
};
......@@ -31,6 +37,7 @@ describe('MemberList', () => {
provide: {
sourceId: 1,
currentUserId: 1,
namespace: MEMBER_TYPES.user,
},
stubs: [
'member-avatar',
......
......@@ -4,12 +4,16 @@ import LdapDropdownItem from 'ee/members/components/ldap/ldap_dropdown_item.vue'
import waitForPromises from 'helpers/wait_for_promises';
import { member } from 'jest/members/mock_data';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
describe('RoleDropdown', () => {
let wrapper;
const createComponent = (propsData = {}) => {
wrapper = shallowMount(RoleDropdown, {
provide: {
namespace: MEMBER_TYPES.user,
},
propsData: {
member,
permissions: {},
......
import { membersJsonString } from 'jest/members/mock_data';
import { MEMBER_TYPES } from '~/members/constants';
import { initMembersApp } from '~/members/index';
describe('initMembersApp', () => {
......@@ -6,7 +7,9 @@ describe('initMembersApp', () => {
let vm;
const createVm = () => {
vm = initMembersApp(el, {});
vm = initMembersApp(el, {
namespace: MEMBER_TYPES.user,
});
};
beforeEach(() => {
......@@ -24,7 +27,7 @@ describe('initMembersApp', () => {
it('sets `ldapOverridePath` in Vuex store', () => {
createVm();
expect(vm.$store.state.ldapOverridePath).toBe(
expect(vm.$store.state[MEMBER_TYPES.user].ldapOverridePath).toBe(
'/groups/ldap-group/-/group_members/:id/override',
);
});
......
# frozen_string_literal: true
module RuboCop
module Cop
module Style
# This cop is based on `Style/RegexpLiteral` but adds a new
# `EnforcedStyle` option `mixed_preserve`.
#
# This cop will be removed once the upstream PR is merged and RuboCop upgraded.
#
# See https://github.com/rubocop/rubocop/pull/9688
class RegexpLiteralMixedPreserve < RuboCop::Cop::Style::RegexpLiteral
module Patch
private
def allowed_slash_literal?(node)
super || allowed_mixed_preserve?(node)
end
def allowed_percent_r_literal?(node)
super || allowed_mixed_preserve?(node)
end
def allowed_mixed_preserve?(node)
style == :mixed_preserve && !contains_disallowed_slash?(node)
end
end
prepend Patch
end
end
end
end
......@@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ApproveAccessRequestButton from '~/members/components/action_buttons/approve_access_request_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
......@@ -14,9 +15,14 @@ describe('ApproveAccessRequestButton', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
modules: {
[MEMBER_TYPES.accessRequest]: {
namespaced: true,
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
},
},
},
});
};
......@@ -25,6 +31,9 @@ describe('ApproveAccessRequestButton', () => {
wrapper = shallowMount(ApproveAccessRequestButton, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.accessRequest,
},
propsData: {
memberId: 1,
...propsData,
......
......@@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RemoveGroupLinkButton from '~/members/components/action_buttons/remove_group_link_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { group } from '../../mock_data';
const localVue = createLocalVue();
......@@ -17,7 +18,12 @@ describe('RemoveGroupLinkButton', () => {
const createStore = () => {
return new Vuex.Store({
actions,
modules: {
[MEMBER_TYPES.group]: {
namespaced: true,
actions,
},
},
});
};
......@@ -25,6 +31,9 @@ describe('RemoveGroupLinkButton', () => {
wrapper = mount(RemoveGroupLinkButton, {
localVue,
store: createStore(),
provide: {
namespace: MEMBER_TYPES.group,
},
propsData: {
groupLink: group,
},
......
......@@ -2,6 +2,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import RemoveMemberButton from '~/members/components/action_buttons/remove_member_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -11,9 +12,14 @@ describe('RemoveMemberButton', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
},
},
},
});
};
......@@ -22,6 +28,9 @@ describe('RemoveMemberButton', () => {
wrapper = shallowMount(RemoveMemberButton, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.user,
},
propsData: {
memberId: 1,
memberType: 'GroupMember',
......
......@@ -3,6 +3,7 @@ import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import ResendInviteButton from '~/members/components/action_buttons/resend_invite_button.vue';
import { MEMBER_TYPES } from '~/members/constants';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
......@@ -14,9 +15,14 @@ describe('ResendInviteButton', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
modules: {
[MEMBER_TYPES.invite]: {
namespaced: true,
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
},
},
},
});
};
......@@ -25,6 +31,9 @@ describe('ResendInviteButton', () => {
wrapper = shallowMount(ResendInviteButton, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.invite,
},
propsData: {
memberId: 1,
...propsData,
......
......@@ -5,6 +5,7 @@ import Vuex from 'vuex';
import * as commonUtils from '~/lib/utils/common_utils';
import MembersApp from '~/members/components/app.vue';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { RECEIVE_MEMBER_ROLE_ERROR, HIDE_ERROR } from '~/members/store/mutation_types';
import mutations from '~/members/store/mutations';
......@@ -17,16 +18,24 @@ describe('MembersApp', () => {
const createComponent = (state = {}, options = {}) => {
store = new Vuex.Store({
state: {
showError: true,
errorMessage: 'Something went wrong, please try again.',
...state,
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
showError: true,
errorMessage: 'Something went wrong, please try again.',
...state,
},
mutations,
},
},
mutations,
});
wrapper = shallowMount(MembersApp, {
localVue,
provide: {
namespace: MEMBER_TYPES.user,
},
store,
...options,
});
......@@ -48,7 +57,9 @@ describe('MembersApp', () => {
it('renders and scrolls to error alert', async () => {
createComponent({ showError: false, errorMessage: '' });
store.commit(RECEIVE_MEMBER_ROLE_ERROR, { error: new Error('Network Error') });
store.commit(`${MEMBER_TYPES.user}/${RECEIVE_MEMBER_ROLE_ERROR}`, {
error: new Error('Network Error'),
});
await nextTick();
......@@ -66,7 +77,7 @@ describe('MembersApp', () => {
it('does not render and scroll to error alert', async () => {
createComponent();
store.commit(HIDE_ERROR);
store.commit(`${MEMBER_TYPES.user}/${HIDE_ERROR}`);
await nextTick();
......
......@@ -3,6 +3,7 @@ import Vuex from 'vuex';
import FilterSortContainer from '~/members/components/filter_sort/filter_sort_container.vue';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -12,22 +13,30 @@ describe('FilterSortContainer', () => {
const createComponent = (state) => {
const store = new Vuex.Store({
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
tableSortableFields: ['account'],
...state,
},
},
tableSortableFields: ['account'],
...state,
},
});
wrapper = shallowMount(FilterSortContainer, {
localVue,
store,
provide: {
namespace: MEMBER_TYPES.user,
},
});
};
......
......@@ -2,6 +2,7 @@ import { GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue';
import { MEMBER_TYPES } from '~/members/constants';
import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
const localVue = createLocalVue();
......@@ -12,15 +13,20 @@ describe('MembersFilteredSearchBar', () => {
const createComponent = ({ state = {}, provide = {} } = {}) => {
const store = new Vuex.Store({
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
...state,
},
},
...state,
},
});
......@@ -29,6 +35,7 @@ describe('MembersFilteredSearchBar', () => {
provide: {
sourceId: 1,
canManageMembers: true,
namespace: MEMBER_TYPES.user,
...provide,
},
store,
......
......@@ -3,6 +3,7 @@ import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import * as urlUtilities from '~/lib/utils/url_utility';
import SortDropdown from '~/members/components/filter_sort/sort_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -14,16 +15,21 @@ describe('SortDropdown', () => {
const createComponent = (state) => {
const store = new Vuex.Store({
state: {
tableSortableFields: ['account', 'granted', 'expires', 'maxRole', 'lastSignIn'],
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
tableSortableFields: ['account', 'granted', 'expires', 'maxRole', 'lastSignIn'],
filteredSearchBar: {
show: true,
tokens: ['two_factor'],
searchParam: 'search',
placeholder: 'Filter members',
recentSearchesStorageKey: 'group_members',
},
...state,
},
},
...state,
},
});
......@@ -31,6 +37,7 @@ describe('SortDropdown', () => {
localVue,
provide: {
sourceId: 1,
namespace: MEMBER_TYPES.user,
},
store,
});
......
......@@ -4,7 +4,7 @@ import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import LeaveModal from '~/members/components/modals/leave_modal.vue';
import { LEAVE_MODAL_ID } from '~/members/constants';
import { LEAVE_MODAL_ID, MEMBER_TYPES } from '~/members/constants';
import { member } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
......@@ -17,9 +17,14 @@ describe('LeaveModal', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
memberPath: '/groups/foo-bar/-/group_members/:id',
...state,
},
},
},
});
};
......@@ -28,6 +33,9 @@ describe('LeaveModal', () => {
wrapper = mount(LeaveModal, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.user,
},
propsData: {
member,
...propsData,
......
......@@ -4,7 +4,7 @@ import { mount, createLocalVue, createWrapper } from '@vue/test-utils';
import { nextTick } from 'vue';
import Vuex from 'vuex';
import RemoveGroupLinkModal from '~/members/components/modals/remove_group_link_modal.vue';
import { REMOVE_GROUP_LINK_MODAL_ID } from '~/members/constants';
import { REMOVE_GROUP_LINK_MODAL_ID, MEMBER_TYPES } from '~/members/constants';
import { group } from '../../mock_data';
jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' }));
......@@ -21,13 +21,18 @@ describe('RemoveGroupLinkModal', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
memberPath: '/groups/foo-bar/-/group_links/:id',
groupLinkToRemove: group,
removeGroupLinkModalVisible: true,
...state,
modules: {
[MEMBER_TYPES.group]: {
namespaced: true,
state: {
memberPath: '/groups/foo-bar/-/group_links/:id',
groupLinkToRemove: group,
removeGroupLinkModalVisible: true,
...state,
},
actions,
},
},
actions,
});
};
......@@ -35,6 +40,9 @@ describe('RemoveGroupLinkModal', () => {
wrapper = mount(RemoveGroupLinkModal, {
localVue,
store: createStore(state),
provide: {
namespace: MEMBER_TYPES.group,
},
attrs: {
static: true,
},
......
......@@ -5,6 +5,7 @@ import Vuex from 'vuex';
import { useFakeDate } from 'helpers/fake_date';
import waitForPromises from 'helpers/wait_for_promises';
import ExpirationDatepicker from '~/members/components/table/expiration_datepicker.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { member } from '../../mock_data';
const localVue = createLocalVue();
......@@ -31,7 +32,11 @@ describe('ExpirationDatepicker', () => {
),
};
return new Vuex.Store({ actions });
return new Vuex.Store({
modules: {
[MEMBER_TYPES.user]: { namespaced: true, actions },
},
});
};
const createComponent = (propsData = {}) => {
......@@ -41,6 +46,9 @@ describe('ExpirationDatepicker', () => {
permissions: { canUpdate: true },
...propsData,
},
provide: {
namespace: MEMBER_TYPES.user,
},
localVue,
store: createStore(),
mocks: {
......
......@@ -14,6 +14,7 @@ import MemberAvatar from '~/members/components/table/member_avatar.vue';
import MemberSource from '~/members/components/table/member_source.vue';
import MembersTable from '~/members/components/table/members_table.vue';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
import * as initUserPopovers from '~/user_popovers';
import { member as memberMock, directMember, invite, accessRequest } from '../../mock_data';
......@@ -25,14 +26,19 @@ describe('MembersTable', () => {
const createStore = (state = {}) => {
return new Vuex.Store({
state: {
members: [],
tableFields: [],
tableAttrs: {
table: { 'data-qa-selector': 'members_list' },
tr: { 'data-qa-selector': 'member_row' },
modules: {
[MEMBER_TYPES.user]: {
namespaced: true,
state: {
members: [],
tableFields: [],
tableAttrs: {
table: { 'data-qa-selector': 'members_list' },
tr: { 'data-qa-selector': 'member_row' },
},
...state,
},
},
...state,
},
});
};
......@@ -44,6 +50,7 @@ describe('MembersTable', () => {
provide: {
sourceId: 1,
currentUserId: 1,
namespace: MEMBER_TYPES.user,
...provide,
},
stubs: [
......
......@@ -7,6 +7,7 @@ import Vuex from 'vuex';
import waitForPromises from 'helpers/wait_for_promises';
import { BV_DROPDOWN_SHOW } from '~/lib/utils/constants';
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { member } from '../../mock_data';
const localVue = createLocalVue();
......@@ -24,11 +25,18 @@ describe('RoleDropdown', () => {
updateMemberRole: jest.fn(() => Promise.resolve()),
};
return new Vuex.Store({ actions });
return new Vuex.Store({
modules: {
[MEMBER_TYPES.user]: { namespaced: true, actions },
},
});
};
const createComponent = (propsData = {}) => {
wrapper = mount(RoleDropdown, {
provide: {
namespace: MEMBER_TYPES.user,
},
propsData: {
member,
permissions: {},
......
import { createWrapper } from '@vue/test-utils';
import MembersApp from '~/members/components/app.vue';
import { MEMBER_TYPES } from '~/members/constants';
import { initMembersApp } from '~/members/index';
import { membersJsonString, members } from './mock_data';
......@@ -10,6 +11,7 @@ describe('initMembersApp', () => {
const setup = () => {
vm = initMembersApp(el, {
namespace: MEMBER_TYPES.user,
tableFields: ['account'],
tableAttrs: { table: { 'data-qa-selector': 'members_list' } },
tableSortableFields: ['account'],
......@@ -45,42 +47,46 @@ describe('initMembersApp', () => {
it('parses and sets `members` in Vuex store', () => {
setup();
expect(vm.$store.state.members).toEqual(members);
expect(vm.$store.state[MEMBER_TYPES.user].members).toEqual(members);
});
it('sets `tableFields` in Vuex store', () => {
setup();
expect(vm.$store.state.tableFields).toEqual(['account']);
expect(vm.$store.state[MEMBER_TYPES.user].tableFields).toEqual(['account']);
});
it('sets `tableAttrs` in Vuex store', () => {
setup();
expect(vm.$store.state.tableAttrs).toEqual({ table: { 'data-qa-selector': 'members_list' } });
expect(vm.$store.state[MEMBER_TYPES.user].tableAttrs).toEqual({
table: { 'data-qa-selector': 'members_list' },
});
});
it('sets `tableSortableFields` in Vuex store', () => {
setup();
expect(vm.$store.state.tableSortableFields).toEqual(['account']);
expect(vm.$store.state[MEMBER_TYPES.user].tableSortableFields).toEqual(['account']);
});
it('sets `requestFormatter` in Vuex store', () => {
setup();
expect(vm.$store.state.requestFormatter()).toEqual({});
expect(vm.$store.state[MEMBER_TYPES.user].requestFormatter()).toEqual({});
});
it('sets `filteredSearchBar` in Vuex store', () => {
setup();
expect(vm.$store.state.filteredSearchBar).toEqual({ show: false });
expect(vm.$store.state[MEMBER_TYPES.user].filteredSearchBar).toEqual({ show: false });
});
it('sets `memberPath` in Vuex store', () => {
setup();
expect(vm.$store.state.memberPath).toBe('/groups/foo-bar/-/group_members/:id');
expect(vm.$store.state[MEMBER_TYPES.user].memberPath).toBe(
'/groups/foo-bar/-/group_members/:id',
);
});
});
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe AddNewTrailPlans, :migration do
describe '#up' do
before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return true
end
it 'creates 2 entries within the plans table' do
expect { migrate! }.to change { AddNewTrailPlans::Plan.count }.by 2
expect(AddNewTrailPlans::Plan.last(2).pluck(:name)).to match_array(%w(ultimate_trial premium_trial))
end
it 'creates 2 entries for plan limits' do
expect { migrate! }.to change { AddNewTrailPlans::PlanLimits.count }.by 2
end
context 'when the plan limits for gold and silver exists' do
before do
table(:plans).create!(id: 1, name: 'gold', title: 'Gold')
table(:plan_limits).create!(id: 1, plan_id: 1, storage_size_limit: 2000)
table(:plans).create!(id: 2, name: 'silver', title: 'Silver')
table(:plan_limits).create!(id: 2, plan_id: 2, storage_size_limit: 1000)
end
it 'duplicates the gold and silvers plan limits entries' do
migrate!
ultimate_plan_limits = AddNewTrailPlans::Plan.find_by(name: 'ultimate_trial').limits
expect(ultimate_plan_limits.storage_size_limit).to be 2000
premium_plan_limits = AddNewTrailPlans::Plan.find_by(name: 'premium_trial').limits
expect(premium_plan_limits.storage_size_limit).to be 1000
end
end
context 'when the instance is not SaaS' do
before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return false
end
it 'does not create plans and plan limits and returns' do
expect { migrate! }.not_to change { AddNewTrailPlans::Plan.count }
expect { migrate! }.not_to change { AddNewTrailPlans::Plan.count }
end
end
end
describe '#down' do
before do
table(:plans).create!(id: 3, name: 'other')
table(:plan_limits).create!(plan_id: 3)
end
context 'when the instance is SaaS' do
before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return true
end
it 'removes the newly added ultimate and premium trial entries' do
migrate!
expect { described_class.new.down }.to change { AddNewTrailPlans::Plan.count }.by(-2)
expect(AddNewTrailPlans::Plan.find_by(name: 'premium_trial')).to be_nil
expect(AddNewTrailPlans::Plan.find_by(name: 'ultimate_trial')).to be_nil
other_plan = AddNewTrailPlans::Plan.find_by(name: 'other')
expect(other_plan).to be_persisted
expect(AddNewTrailPlans::PlanLimits.count).to eq(1)
expect(AddNewTrailPlans::PlanLimits.first.plan_id).to eq(other_plan.id)
end
end
context 'when the instance is not SaaS' do
before do
allow(Gitlab).to receive(:dev_env_or_com?).and_return false
table(:plans).create!(id: 1, name: 'ultimate_trial', title: 'Ultimate Trial')
table(:plans).create!(id: 2, name: 'premium_trial', title: 'Premium Trial')
table(:plan_limits).create!(id: 1, plan_id: 1)
table(:plan_limits).create!(id: 2, plan_id: 2)
end
it 'does not delete plans and plan limits and returns' do
migrate!
expect { described_class.new.down }.not_to change { AddNewTrailPlans::Plan.count }
expect(AddNewTrailPlans::PlanLimits.count).to eq(3)
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require_relative '../../../../rubocop/cop/style/regexp_literal_mixed_preserve'
# This spec contains only relevant examples.
#
# See also https://github.com/rubocop/rubocop/pull/9688
RSpec.describe RuboCop::Cop::Style::RegexpLiteralMixedPreserve, :config do
let(:config) do
supported_styles = { 'SupportedStyles' => %w[slashes percent_r mixed mixed_preserve] }
RuboCop::Config.new('Style/PercentLiteralDelimiters' =>
percent_literal_delimiters_config,
'Style/RegexpLiteralMixedPreserve' =>
cop_config.merge(supported_styles))
end
let(:percent_literal_delimiters_config) { { 'PreferredDelimiters' => { '%r' => '{}' } } }
context 'when EnforcedStyle is set to mixed_preserve' do
let(:cop_config) { { 'EnforcedStyle' => 'mixed_preserve' } }
describe 'a single-line `//` regex without slashes' do
it 'is accepted' do
expect_no_offenses('foo = /a/')
end
end
describe 'a single-line `//` regex with slashes' do
it 'registers an offense and corrects' do
expect_offense(<<~'RUBY')
foo = /home\//
^^^^^^^^ Use `%r` around regular expression.
RUBY
expect_correction(<<~'RUBY')
foo = %r{home/}
RUBY
end
describe 'when configured to allow inner slashes' do
before do
cop_config['AllowInnerSlashes'] = true
end
it 'is accepted' do
expect_no_offenses('foo = /home\\//')
end
end
end
describe 'a multi-line `//` regex without slashes' do
it 'is accepted' do
expect_no_offenses(<<~'RUBY')
foo = /
foo
bar
/x
RUBY
end
end
describe 'a multi-line `//` regex with slashes' do
it 'registers an offense and corrects' do
expect_offense(<<~'RUBY')
foo = /
^ Use `%r` around regular expression.
https?:\/\/
example\.com
/x
RUBY
expect_correction(<<~'RUBY')
foo = %r{
https?://
example\.com
}x
RUBY
end
end
describe 'a single-line %r regex without slashes' do
it 'is accepted' do
expect_no_offenses(<<~RUBY)
foo = %r{a}
RUBY
end
end
describe 'a single-line %r regex with slashes' do
it 'is accepted' do
expect_no_offenses('foo = %r{home/}')
end
describe 'when configured to allow inner slashes' do
before do
cop_config['AllowInnerSlashes'] = true
end
it 'is accepted' do
expect_no_offenses(<<~RUBY)
foo = %r{home/}
RUBY
end
end
end
describe 'a multi-line %r regex without slashes' do
it 'is accepted' do
expect_no_offenses(<<~RUBY)
foo = %r{
foo
bar
}x
RUBY
end
end
describe 'a multi-line %r regex with slashes' do
it 'is accepted' do
expect_no_offenses(<<~RUBY)
foo = %r{
https?://
example\.com
}x
RUBY
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