diff --git a/.rubocop.yml b/.rubocop.yml
index 89a9cbc3744..a19cba5eb46 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -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
diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml
index 1073f6a04d1..786f81936e5 100644
--- a/.rubocop_manual_todo.yml
+++ b/.rubocop_manual_todo.yml
@@ -176,8 +176,6 @@ Rails/SaveBang:
- 'spec/lib/gitlab/database/custom_structure_spec.rb'
- 'spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb'
- 'spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb'
- - 'spec/lib/gitlab/email/handler/create_note_handler_spec.rb'
- - 'spec/lib/gitlab/email/handler/unsubscribe_handler_spec.rb'
- 'spec/lib/gitlab/gfm/reference_rewriter_spec.rb'
- 'spec/lib/gitlab/git_access_spec.rb'
- 'spec/lib/gitlab/import_export/avatar_saver_spec.rb'
@@ -3332,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'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index f06496d4519..cfcf990029d 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -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:
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index f7cfe80df5c..829a9d64cb7 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -87,7 +87,7 @@ export default {
@input="searchMergeRequests"
@removeToken="setSearchType(null)"
/>
-
+
-
+
{{ searchType.label }}
diff --git a/app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue b/app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue
index 83f266779f2..00973100e15 100644
--- a/app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue
+++ b/app/assets/javascripts/members/components/action_buttons/approve_access_request_button.vue
@@ -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`);
},
diff --git a/app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue b/app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue
index 3b87c29c1bc..fef7940eaa2 100644
--- a/app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue
+++ b/app/assets/javascripts/members/components/action_buttons/remove_group_link_button.vue
@@ -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);
+ },
+ }),
},
};
diff --git a/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue b/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
index 9954da3e0d4..fc5fcdc52c5 100644
--- a/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
+++ b/app/assets/javascripts/members/components/action_buttons/remove_member_button.vue
@@ -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);
},
diff --git a/app/assets/javascripts/members/components/action_buttons/resend_invite_button.vue b/app/assets/javascripts/members/components/action_buttons/resend_invite_button.vue
index 261a6279920..2173974c6f4 100644
--- a/app/assets/javascripts/members/components/action_buttons/resend_invite_button.vue
+++ b/app/assets/javascripts/members/components/action_buttons/resend_invite_button.vue
@@ -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`);
},
diff --git a/app/assets/javascripts/members/components/app.vue b/app/assets/javascripts/members/components/app.vue
index 27fceb7374e..585fabdf3ff 100644
--- a/app/assets/javascripts/members/components/app.vue
+++ b/app/assets/javascripts/members/components/app.vue
@@ -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}`);
+ },
}),
},
};
diff --git a/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue b/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue
index 812a8626949..419b7b83c0f 100644
--- a/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue
+++ b/app/assets/javascripts/members/components/filter_sort/filter_sort_container.vue
@@ -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;
},
diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue
index 9e58c7022b8..cc97d235a9c 100644
--- a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue
+++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue
@@ -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 (
diff --git a/app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue b/app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue
index 9fa8772faf4..ce28283ccdf 100644
--- a/app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue
+++ b/app/assets/javascripts/members/components/filter_sort/sort_dropdown.vue
@@ -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);
},
diff --git a/app/assets/javascripts/members/components/modals/leave_modal.vue b/app/assets/javascripts/members/components/modals/leave_modal.vue
index a0f978d85cc..b4cbef13b75 100644
--- a/app/assets/javascripts/members/components/modals/leave_modal.vue
+++ b/app/assets/javascripts/members/components/modals/leave_modal.vue
@@ -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');
},
diff --git a/app/assets/javascripts/members/components/modals/remove_group_link_modal.vue b/app/assets/javascripts/members/components/modals/remove_group_link_modal.vue
index 1ba6bf9aba6..b179ced46e1 100644
--- a/app/assets/javascripts/members/components/modals/remove_group_link_modal.vue
+++ b/app/assets/javascripts/members/components/modals/remove_group_link_modal.vue
@@ -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();
},
diff --git a/app/assets/javascripts/members/components/table/expiration_datepicker.vue b/app/assets/javascripts/members/components/table/expiration_datepicker.vue
index 0a8af81c1d1..9f6e8979102 100644
--- a/app/assets/javascripts/members/components/table/expiration_datepicker.vue
+++ b/app/assets/javascripts/members/components/table/expiration_datepicker.vue
@@ -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({
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index 5db29951d94..236aeaef418 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -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),
diff --git a/app/assets/javascripts/members/components/table/role_dropdown.vue b/app/assets/javascripts/members/components/table/role_dropdown.vue
index 8ad45ab6920..f84ded427cd 100644
--- a/app/assets/javascripts/members/components/table/role_dropdown.vue
+++ b/app/assets/javascripts/members/components/table/role_dropdown.vue
@@ -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;
diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js
index 2f3589bbf6a..6376b3fa75a 100644
--- a/app/assets/javascripts/members/index.js
+++ b/app/assets/javascripts/members/index.js
@@ -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,
diff --git a/app/assets/javascripts/members/store/index.js b/app/assets/javascripts/members/store/index.js
index 45f4eefffc9..6c371887a3f 100644
--- a/app/assets/javascripts/members/store/index.js
+++ b/app/assets/javascripts/members/store/index.js
@@ -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,
diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js
index ab70fa572ba..b0a70055835 100644
--- a/app/assets/javascripts/pages/groups/group_members/index.js
+++ b/app/assets/javascripts/pages/groups/group_members/index.js
@@ -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,
});
diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js
index 4aea5614bfb..471798d2931 100644
--- a/app/assets/javascripts/pages/projects/project_members/index.js
+++ b/app/assets/javascripts/pages/projects/project_members/index.js
@@ -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,
});
diff --git a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
index 9d12d228d35..51a8eb5ca69 100644
--- a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
+++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js
@@ -47,10 +47,15 @@ export default class PerformanceBarStore {
}
canTrackRequest(requestUrl) {
- return (
- requestUrl.endsWith('/api/graphql') ||
- this.requests.filter((request) => request.url === requestUrl).length < 2
- );
+ // We want to store at most 2 unique requests per URL, as additional
+ // requests to the same URL probably aren't very interesting.
+ //
+ // GraphQL requests are the exception: because all GraphQL requests
+ // go to the same URL, we set a higher limit of 10 to allow
+ // capturing different queries a page may make.
+ const requestsLimit = requestUrl.endsWith('/api/graphql') ? 10 : 2;
+
+ return this.requests.filter((request) => request.url === requestUrl).length < requestsLimit;
}
static truncateUrl(requestUrl) {
diff --git a/app/assets/javascripts/pipelines/components/graph/constants.js b/app/assets/javascripts/pipelines/components/graph/constants.js
index 216b6c6737c..dd9cdae518f 100644
--- a/app/assets/javascripts/pipelines/components/graph/constants.js
+++ b/app/assets/javascripts/pipelines/components/graph/constants.js
@@ -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';
diff --git a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
index 78fee6a75a8..6451605a222 100644
--- a/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
+++ b/app/assets/javascripts/pipelines/components/graph/job_group_dropdown.vue
@@ -1,5 +1,6 @@