Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/header_search/components/app.vue80
-rw-r--r--app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue5
-rw-r--r--app/assets/javascripts/header_search/components/header_search_default_items.vue5
-rw-r--r--app/assets/javascripts/header_search/components/header_search_scoped_items.vue19
-rw-r--r--app/assets/javascripts/header_search/constants.js22
-rw-r--r--app/assets/javascripts/invite_members/components/invite_members_modal.vue37
-rw-r--r--app/assets/javascripts/invite_members/constants.js9
-rw-r--r--app/assets/javascripts/invite_members/init_invite_members_modal.js2
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue28
-rw-r--r--app/assets/javascripts/security_configuration/components/training_provider_list.vue36
-rw-r--r--app/controllers/confirmations_controller.rb1
-rw-r--r--app/controllers/repositories/git_http_client_controller.rb13
-rw-r--r--app/helpers/invite_members_helper.rb27
-rw-r--r--app/services/ci/play_build_service.rb2
-rw-r--r--app/services/members/create_service.rb11
-rw-r--r--app/views/devise/confirmations/almost_there.haml2
-rw-r--r--config/feature_flags/experiment/member_areas_of_focus.yml8
-rw-r--r--config/initializers/wikicloth_patch.rb159
-rw-r--r--db/migrate/20211129151155_add_migrated_to_new_structure_column_to_vulnerability_occurrences.rb7
-rw-r--r--db/migrate/20211129151832_add_index_on_vulnerability_occurrences_migrated_to_new_structure_column.rb15
-rw-r--r--db/post_migrate/20211201101541_drop_clusters_applications_runners_ci_runners_fk.rb15
-rw-r--r--db/schema_migrations/202111291511551
-rw-r--r--db/schema_migrations/202111291518321
-rw-r--r--db/schema_migrations/202112011015411
-rw-r--r--db/structure.sql6
-rw-r--r--doc/api/geo_nodes.md14
-rw-r--r--doc/api/invitations.md1
-rw-r--r--doc/api/members.md1
-rw-r--r--doc/user/project/issue_board.md8
-rw-r--r--lib/api/invitations.rb1
-rw-r--r--lib/api/members.rb1
-rw-r--r--locale/gitlab.pot105
-rw-r--r--spec/controllers/repositories/git_http_controller_spec.rb8
-rw-r--r--spec/features/groups/members/manage_members_spec.rb54
-rw-r--r--spec/features/one_trust_spec.rb23
-rw-r--r--spec/frontend/header_search/components/app_spec.js72
-rw-r--r--spec/frontend/header_search/components/header_search_autocomplete_items_spec.js14
-rw-r--r--spec/frontend/header_search/components/header_search_default_items_spec.js14
-rw-r--r--spec/frontend/header_search/components/header_search_scoped_items_spec.js23
-rw-r--r--spec/frontend/invite_members/components/invite_members_modal_spec.js80
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js11
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js60
-rw-r--r--spec/helpers/invite_members_helper_spec.rb54
-rw-r--r--spec/requests/api/invitations_spec.rb14
-rw-r--r--spec/requests/api/members_spec.rb27
-rw-r--r--spec/services/ci/play_build_service_spec.rb17
-rw-r--r--spec/services/members/create_service_spec.rb70
-rw-r--r--spec/support/helpers/features/invite_members_modal_helper.rb10
-rw-r--r--spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb10
49 files changed, 694 insertions, 510 deletions
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index a575b80facc..67e3998bc97 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -2,9 +2,14 @@
import { GlSearchBoxByType, GlOutsideDirective as Outside } from '@gitlab/ui';
import { mapState, mapActions, mapGetters } from 'vuex';
import { visitUrl } from '~/lib/utils/url_utility';
-import { __ } from '~/locale';
+import { s__, sprintf } from '~/locale';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
-import { FIRST_DROPDOWN_INDEX, SEARCH_BOX_INDEX } from '../constants';
+import {
+ FIRST_DROPDOWN_INDEX,
+ SEARCH_BOX_INDEX,
+ SEARCH_INPUT_DESCRIPTION,
+ SEARCH_RESULTS_DESCRIPTION,
+} from '../constants';
import HeaderSearchAutocompleteItems from './header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from './header_search_default_items.vue';
import HeaderSearchScopedItems from './header_search_scoped_items.vue';
@@ -12,7 +17,21 @@ import HeaderSearchScopedItems from './header_search_scoped_items.vue';
export default {
name: 'HeaderSearchApp',
i18n: {
- searchPlaceholder: __('Search or jump to...'),
+ searchPlaceholder: s__('GlobalSearch|Search or jump to...'),
+ searchAria: s__('GlobalSearch|Search GitLab'),
+ searchInputDescribeByNoDropdown: s__(
+ 'GlobalSearch|Type and press the enter key to submit search.',
+ ),
+ searchInputDescribeByWithDropdown: s__(
+ 'GlobalSearch|Type for new suggestions to appear below.',
+ ),
+ searchDescribedByDefault: s__(
+ 'GlobalSearch|%{count} default results provided. Use the up and down arrow keys to navigate search results list.',
+ ),
+ searchDescribedByUpdated: s__(
+ 'GlobalSearch|Results updated. %{count} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.',
+ ),
+ searchResultsLoading: s__('GlobalSearch|Search results are loading'),
},
directives: { Outside },
components: {
@@ -29,7 +48,7 @@ export default {
};
},
computed: {
- ...mapState(['search']),
+ ...mapState(['search', 'loading']),
...mapGetters(['searchQuery', 'searchOptions']),
searchText: {
get() {
@@ -42,6 +61,9 @@ export default {
currentFocusedOption() {
return this.searchOptions[this.currentFocusIndex];
},
+ currentFocusedId() {
+ return this.currentFocusedOption?.html_id;
+ },
isLoggedIn() {
return gon?.current_username;
},
@@ -58,6 +80,30 @@ export default {
return FIRST_DROPDOWN_INDEX;
},
+ searchInputDescribeBy() {
+ if (this.isLoggedIn) {
+ return this.$options.i18n.searchInputDescribeByWithDropdown;
+ }
+
+ return this.$options.i18n.searchInputDescribeByNoDropdown;
+ },
+ dropdownResultsDescription() {
+ if (!this.showSearchDropdown) {
+ return ''; // This allows aria-live to see register an update when the dropdown is shown
+ }
+
+ if (this.showDefaultItems) {
+ return sprintf(this.$options.i18n.searchDescribedByDefault, {
+ count: this.searchOptions.length,
+ });
+ }
+
+ return this.loading
+ ? this.$options.i18n.searchResultsLoading
+ : sprintf(this.$options.i18n.searchDescribedByUpdated, {
+ count: this.searchOptions.length,
+ });
+ },
},
methods: {
...mapActions(['setSearch', 'fetchAutocompleteOptions']),
@@ -79,22 +125,44 @@ export default {
},
},
SEARCH_BOX_INDEX,
+ SEARCH_INPUT_DESCRIPTION,
+ SEARCH_RESULTS_DESCRIPTION,
};
</script>
<template>
- <section v-outside="closeDropdown" class="header-search gl-relative">
+ <form
+ v-outside="closeDropdown"
+ role="search"
+ :aria-label="$options.i18n.searchAria"
+ class="header-search gl-relative"
+ >
<gl-search-box-by-type
v-model="searchText"
+ role="searchbox"
class="gl-z-index-1"
:debounce="500"
autocomplete="off"
:placeholder="$options.i18n.searchPlaceholder"
+ :aria-activedescendant="currentFocusedId"
+ :aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
@focus="openDropdown"
@click="openDropdown"
@input="getAutocompleteOptions"
@keydown.enter.stop.prevent="submitSearch"
/>
+ <span :id="$options.SEARCH_INPUT_DESCRIPTION" role="region" class="gl-sr-only">{{
+ searchInputDescribeBy
+ }}</span>
+ <span
+ role="region"
+ :data-testid="$options.SEARCH_RESULTS_DESCRIPTION"
+ class="gl-sr-only"
+ aria-live="polite"
+ aria-atomic="true"
+ >
+ {{ dropdownResultsDescription }}
+ </span>
<div
v-if="showSearchDropdown"
data-testid="header-search-dropdown-menu"
@@ -118,5 +186,5 @@ export default {
</template>
</div>
</div>
- </section>
+ </form>
</template>
diff --git a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
index cf1f7c030e2..9f4f4768247 100644
--- a/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_autocomplete_items.vue
@@ -69,13 +69,16 @@ export default {
<gl-dropdown-section-header>{{ option.category }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="data in option.data"
+ :id="data.html_id"
:ref="data.html_id"
:key="data.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(data) }"
+ :aria-selected="isOptionFocused(data)"
+ :aria-label="data.label"
tabindex="-1"
:href="data.url"
>
- <div class="gl-display-flex gl-align-items-center">
+ <div class="gl-display-flex gl-align-items-center" aria-hidden="true">
<gl-avatar
v-if="data.avatar_url !== undefined"
:src="data.avatar_url"
diff --git a/app/assets/javascripts/header_search/components/header_search_default_items.vue b/app/assets/javascripts/header_search/components/header_search_default_items.vue
index 2228bc0c2a2..53e63bc6cca 100644
--- a/app/assets/javascripts/header_search/components/header_search_default_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_default_items.vue
@@ -43,13 +43,16 @@ export default {
<gl-dropdown-section-header>{{ sectionHeader }}</gl-dropdown-section-header>
<gl-dropdown-item
v-for="option in defaultSearchOptions"
+ :id="option.html_id"
:ref="option.html_id"
:key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
+ :aria-selected="isOptionFocused(option)"
+ :aria-label="option.title"
tabindex="-1"
:href="option.url"
>
- {{ option.title }}
+ <span aria-hidden="true">{{ option.title }}</span>
</gl-dropdown-item>
</div>
</template>
diff --git a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
index d3def929752..3aebee71509 100644
--- a/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
+++ b/app/assets/javascripts/header_search/components/header_search_scoped_items.vue
@@ -1,6 +1,7 @@
<script>
import { GlDropdownItem } from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
+import { __, sprintf } from '~/locale';
export default {
name: 'HeaderSearchScopedItems',
@@ -22,6 +23,13 @@ export default {
isOptionFocused(option) {
return this.currentFocusedOption?.html_id === option.html_id;
},
+ ariaLabel(option) {
+ return sprintf(__('%{search} %{description} %{scope}'), {
+ search: this.search,
+ description: option.description,
+ scope: option.scope || '',
+ });
+ },
},
};
</script>
@@ -30,15 +38,20 @@ export default {
<div>
<gl-dropdown-item
v-for="option in scopedSearchOptions"
+ :id="option.html_id"
:ref="option.html_id"
:key="option.html_id"
:class="{ 'gl-bg-gray-50': isOptionFocused(option) }"
+ :aria-selected="isOptionFocused(option)"
+ :aria-label="ariaLabel(option)"
tabindex="-1"
:href="option.url"
>
- "<span class="gl-font-weight-bold">{{ search }}</span
- >" {{ option.description }}
- <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ <span aria-hidden="true">
+ "<span class="gl-font-weight-bold">{{ search }}</span
+ >" {{ option.description }}
+ <span v-if="option.scope" class="gl-font-style-italic">{{ option.scope }}</span>
+ </span>
</gl-dropdown-item>
</div>
</template>
diff --git a/app/assets/javascripts/header_search/constants.js b/app/assets/javascripts/header_search/constants.js
index 34777697863..b2e45fcd648 100644
--- a/app/assets/javascripts/header_search/constants.js
+++ b/app/assets/javascripts/header_search/constants.js
@@ -1,20 +1,20 @@
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
-export const MSG_ISSUES_ASSIGNED_TO_ME = __('Issues assigned to me');
+export const MSG_ISSUES_ASSIGNED_TO_ME = s__('GlobalSearch|Issues assigned to me');
-export const MSG_ISSUES_IVE_CREATED = __("Issues I've created");
+export const MSG_ISSUES_IVE_CREATED = s__("GlobalSearch|Issues I've created");
-export const MSG_MR_ASSIGNED_TO_ME = __('Merge requests assigned to me');
+export const MSG_MR_ASSIGNED_TO_ME = s__('GlobalSearch|Merge requests assigned to me');
-export const MSG_MR_IM_REVIEWER = __("Merge requests that I'm a reviewer");
+export const MSG_MR_IM_REVIEWER = s__("GlobalSearch|Merge requests that I'm a reviewer");
-export const MSG_MR_IVE_CREATED = __("Merge requests I've created");
+export const MSG_MR_IVE_CREATED = s__("GlobalSearch|Merge requests I've created");
-export const MSG_IN_ALL_GITLAB = __('in all GitLab');
+export const MSG_IN_ALL_GITLAB = s__('GlobalSearch|in all GitLab');
-export const MSG_IN_GROUP = __('in group');
+export const MSG_IN_GROUP = s__('GlobalSearch|in group');
-export const MSG_IN_PROJECT = __('in project');
+export const MSG_IN_PROJECT = s__('GlobalSearch|in project');
export const GROUPS_CATEGORY = 'Groups';
@@ -27,3 +27,7 @@ export const SMALL_AVATAR_PX = 16;
export const FIRST_DROPDOWN_INDEX = 0;
export const SEARCH_BOX_INDEX = -1;
+
+export const SEARCH_INPUT_DESCRIPTION = 'search-input-description';
+
+export const SEARCH_RESULTS_DESCRIPTION = 'search-results-description';
diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
index a6e747c6b48..7163d1be773 100644
--- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue
+++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue
@@ -23,7 +23,6 @@ import {
INVITE_MEMBERS_IN_COMMENT,
GROUP_FILTERS,
USERS_FILTER_ALL,
- MEMBER_AREAS_OF_FOCUS,
INVITE_MEMBERS_FOR_TASK,
MODAL_LABELS,
LEARN_GITLAB,
@@ -101,14 +100,6 @@ export default {
type: String,
required: true,
},
- areasOfFocusOptions: {
- type: Array,
- required: true,
- },
- noSelectionAreasOfFocus: {
- type: Array,
- required: true,
- },
tasksToBeDoneOptions: {
type: Array,
required: true,
@@ -126,7 +117,6 @@ export default {
inviteeType: 'members',
newUsersToInvite: [],
selectedDate: undefined,
- selectedAreasOfFocus: [],
selectedTasksToBeDone: [],
selectedTaskProject: this.projects[0],
groupToBeSharedWith: {},
@@ -182,16 +172,6 @@ export default {
this.newUsersToInvite.length === 0 && Object.keys(this.groupToBeSharedWith).length === 0
);
},
- areasOfFocusEnabled() {
- return !this.tasksToBeDoneEnabled && this.areasOfFocusOptions.length !== 0;
- },
- areasOfFocusForPost() {
- if (this.selectedAreasOfFocus.length === 0 && this.areasOfFocusEnabled) {
- return this.noSelectionAreasOfFocus;
- }
-
- return this.selectedAreasOfFocus;
- },
errorFieldDescription() {
if (this.inviteeType === 'group') {
return '';
@@ -232,8 +212,6 @@ export default {
this.openModal(options);
if (this.isOnLearnGitlab) {
this.trackEvent(INVITE_MEMBERS_FOR_TASK.name, this.source);
- } else {
- this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.view);
}
});
@@ -280,8 +258,6 @@ export default {
if (this.source === INVITE_MEMBERS_IN_COMMENT) {
this.trackEvent(INVITE_MEMBERS_IN_COMMENT, 'comment_invite_success');
}
-
- this.trackEvent(MEMBER_AREAS_OF_FOCUS.name, MEMBER_AREAS_OF_FOCUS.submit);
},
trackinviteMembersForTask() {
const label = 'selected_tasks_to_be_done';
@@ -296,7 +272,6 @@ export default {
this.newUsersToInvite = [];
this.groupToBeSharedWith = {};
this.invalidFeedbackMessage = '';
- this.selectedAreasOfFocus = [];
this.selectedTasksToBeDone = [];
[this.selectedTaskProject] = this.projects;
},
@@ -350,7 +325,6 @@ export default {
email: usersToInviteByEmail,
access_level: this.selectedAccessLevel,
invite_source: this.source,
- areas_of_focus: this.areasOfFocusForPost,
tasks_to_be_done: this.tasksToBeDoneForPost,
tasks_project_id: this.tasksProjectForPost,
};
@@ -361,7 +335,6 @@ export default {
user_id: usersToAddById,
access_level: this.selectedAccessLevel,
invite_source: this.source,
- areas_of_focus: this.areasOfFocusForPost,
tasks_to_be_done: this.tasksToBeDoneForPost,
tasks_project_id: this.tasksProjectForPost,
};
@@ -517,16 +490,6 @@ export default {
</template>
</gl-datepicker>
</div>
- <div v-if="areasOfFocusEnabled">
- <label class="gl-mt-5">
- {{ $options.labels.areasOfFocusLabel }}
- </label>
- <gl-form-checkbox-group
- v-model="selectedAreasOfFocus"
- :options="areasOfFocusOptions"
- data-testid="area-of-focus-checks"
- />
- </div>
<div v-if="showTasksToBeDone" data-testid="invite-members-modal-tasks-to-be-done">
<label class="gl-mt-5">
{{ $options.labels.members.tasksToBeDone.title }}
diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js
index 2a4e7041ed1..87d2fbc6aac 100644
--- a/app/assets/javascripts/invite_members/constants.js
+++ b/app/assets/javascripts/invite_members/constants.js
@@ -3,11 +3,6 @@ import { __, s__ } from '~/locale';
export const SEARCH_DELAY = 200;
export const INVITE_MEMBERS_IN_COMMENT = 'invite_members_in_comment';
-export const MEMBER_AREAS_OF_FOCUS = {
- name: 'member_areas_of_focus',
- view: 'view',
- submit: 'submit',
-};
export const INVITE_MEMBERS_FOR_TASK = {
minimum_access_level: 30,
name: 'invite_members_for_task',
@@ -77,9 +72,6 @@ export const READ_MORE_TEXT = s__(
export const INVITE_BUTTON_TEXT = s__('InviteMembersModal|Invite');
export const CANCEL_BUTTON_TEXT = s__('InviteMembersModal|Cancel');
export const HEADER_CLOSE_LABEL = s__('InviteMembersModal|Close invite team members');
-export const AREAS_OF_FOCUS_LABEL = s__(
- 'InviteMembersModal|What would you like new member(s) to focus on? (optional)',
-);
export const MODAL_LABELS = {
members: {
@@ -142,7 +134,6 @@ export const MODAL_LABELS = {
inviteButtonText: INVITE_BUTTON_TEXT,
cancelButtonText: CANCEL_BUTTON_TEXT,
headerCloseLabel: HEADER_CLOSE_LABEL,
- areasOfFocusLabel: AREAS_OF_FOCUS_LABEL,
};
export const LEARN_GITLAB = 'learn_gitlab';
diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js
index fc657a064dd..2cc056f2ddb 100644
--- a/app/assets/javascripts/invite_members/init_invite_members_modal.js
+++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js
@@ -40,10 +40,8 @@ export default function initInviteMembersModal() {
defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10),
groupSelectFilter: el.dataset.groupsFilter,
groupSelectParentId: parseInt(el.dataset.parentId, 10),
- areasOfFocusOptions: JSON.parse(el.dataset.areasOfFocusOptions),
tasksToBeDoneOptions: JSON.parse(el.dataset.tasksToBeDoneOptions || '[]'),
projects: JSON.parse(el.dataset.projects || '[]'),
- noSelectionAreasOfFocus: JSON.parse(el.dataset.noSelectionAreasOfFocus),
usersFilter: el.dataset.usersFilter,
filterId: parseInt(el.dataset.filterId, 10),
},
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index cd2add6407f..62c30c941eb 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -8,6 +8,7 @@ import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue';
+import TrainingProviderList from './training_provider_list.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
@@ -28,8 +29,28 @@ export const i18n = {
securityTraining: s__('SecurityConfiguration|Security training'),
};
+// This will be removed and replaced with GraphQL query:
+// https://gitlab.com/gitlab-org/gitlab/-/issues/346480
+export const TRAINING_PROVIDERS = [
+ {
+ id: 101,
+ name: __('Kontra'),
+ description: __('Interactive developer security education.'),
+ url: 'https://application.security/',
+ isEnabled: false,
+ },
+ {
+ id: 102,
+ name: __('SecureCodeWarrior'),
+ description: __('Security training with guide and learning pathways.'),
+ url: 'https://www.securecodewarrior.com/',
+ isEnabled: true,
+ },
+];
+
export default {
i18n,
+ TRAINING_PROVIDERS,
components: {
AutoDevOpsAlert,
AutoDevOpsEnabledAlert,
@@ -43,6 +64,7 @@ export default {
SectionLayout,
UpgradeBanner,
UserCalloutDismisser,
+ TrainingProviderList,
},
mixins: [glFeatureFlagsMixin()],
inject: ['projectPath'],
@@ -240,7 +262,11 @@ export default {
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
>
- <section-layout :heading="$options.i18n.securityTraining" />
+ <section-layout :heading="$options.i18n.securityTraining">
+ <template #features>
+ <training-provider-list :providers="$options.TRAINING_PROVIDERS" />
+ </template>
+ </section-layout>
</gl-tab>
</gl-tabs>
</article>
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
new file mode 100644
index 00000000000..160540f6989
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -0,0 +1,36 @@
+<script>
+import { GlCard, GlToggle, GlLink } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlCard,
+ GlToggle,
+ GlLink,
+ },
+ props: {
+ providers: {
+ type: Array,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <ul class="gl-list-style-none gl-m-0 gl-p-0">
+ <li v-for="{ id, isEnabled, name, description, url } in providers" :key="id" class="gl-mb-6">
+ <gl-card>
+ <div class="gl-display-flex">
+ <gl-toggle :value="isEnabled" :label="__('Training mode')" label-position="hidden" />
+ <div class="gl-ml-5">
+ <h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3>
+ <p>
+ {{ description }}
+ <gl-link :href="url" target="_blank">{{ __('Learn more.') }}</gl-link>
+ </p>
+ </div>
+ </div>
+ </gl-card>
+ </li>
+ </ul>
+</template>
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 6725e19df25..dd30d688fa8 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -3,6 +3,7 @@
class ConfirmationsController < Devise::ConfirmationsController
include AcceptsPendingInvitations
include GitlabRecaptcha
+ include OneTrustCSP
prepend_before_action :check_recaptcha, only: :create
before_action :load_recaptcha, only: :new
diff --git a/app/controllers/repositories/git_http_client_controller.rb b/app/controllers/repositories/git_http_client_controller.rb
index b3adda8c633..c002c9b83f9 100644
--- a/app/controllers/repositories/git_http_client_controller.rb
+++ b/app/controllers/repositories/git_http_client_controller.rb
@@ -8,12 +8,9 @@ module Repositories
attr_reader :authentication_result, :redirected_path
- delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
+ delegate :authentication_abilities, to: :authentication_result, allow_nil: true
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
- alias_method :user, :actor
- alias_method :authenticated_user, :actor
-
# Git clients will not know what authenticity token to send along
skip_around_action :set_session_storage
skip_before_action :verify_authenticity_token
@@ -22,8 +19,16 @@ module Repositories
feature_category :source_code_management
+ def authenticated_user
+ authentication_result&.user || authentication_result&.deploy_token
+ end
+
private
+ def user
+ authenticated_user
+ end
+
def download_request?
raise NotImplementedError
end
diff --git a/app/helpers/invite_members_helper.rb b/app/helpers/invite_members_helper.rb
index 25d605ca8ee..8b26b646fdd 100644
--- a/app/helpers/invite_members_helper.rb
+++ b/app/helpers/invite_members_helper.rb
@@ -35,13 +35,6 @@ module InviteMembersHelper
default_access_level: Gitlab::Access::GUEST
}
- experiment(:member_areas_of_focus, user: current_user) do |e|
- e.publish_to_database
-
- e.control { dataset.merge!(areas_of_focus_options: [], no_selection_areas_of_focus: []) }
- e.candidate { dataset.merge!(areas_of_focus_options: member_areas_of_focus_options.to_json, no_selection_areas_of_focus: ['no_selection']) }
- end
-
if show_invite_members_for_task?(source)
dataset.merge!(
tasks_to_be_done_options: tasks_to_be_done_options.to_json,
@@ -55,26 +48,6 @@ module InviteMembersHelper
private
- def member_areas_of_focus_options
- [
- {
- value: 'Contribute to the codebase', text: s_('InviteMembersModal|Contribute to the codebase')
- },
- {
- value: 'Collaborate on open issues and merge requests', text: s_('InviteMembersModal|Collaborate on open issues and merge requests')
- },
- {
- value: 'Configure CI/CD', text: s_('InviteMembersModal|Configure CI/CD')
- },
- {
- value: 'Configure security features', text: s_('InviteMembersModal|Configure security features')
- },
- {
- value: 'Other', text: s_('InviteMembersModal|Other')
- }
- ]
- end
-
# Overridden in EE
def users_filter_data(group)
{}
diff --git a/app/services/ci/play_build_service.rb b/app/services/ci/play_build_service.rb
index c1cf06a4631..e2673c763f3 100644
--- a/app/services/ci/play_build_service.rb
+++ b/app/services/ci/play_build_service.rb
@@ -9,7 +9,7 @@ module Ci
#
if build.enqueue
build.tap do |build|
- build.update(user: current_user, job_variables_attributes: job_variables_attributes || [])
+ build.update!(user: current_user, job_variables_attributes: job_variables_attributes || [])
AfterRequeueJobService.new(project, current_user).execute(build)
end
diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb
index d9ee560c445..acd00d0d1ec 100644
--- a/app/services/members/create_service.rb
+++ b/app/services/members/create_service.rb
@@ -92,7 +92,6 @@ module Members
super
track_invite_source(member)
- track_areas_of_focus(member)
end
def track_invite_source(member)
@@ -110,12 +109,6 @@ module Members
member.invite? ? 'net_new_user' : 'existing_user'
end
- def track_areas_of_focus(member)
- areas_of_focus.each do |area_of_focus|
- Gitlab::Tracking.event(self.class.name, 'area_of_focus', label: area_of_focus, property: member.id.to_s)
- end
- end
-
def create_tasks_to_be_done
return if params[:tasks_to_be_done].blank? || params[:tasks_project_id].blank?
@@ -128,10 +121,6 @@ module Members
TasksToBeDone::CreateWorker.perform_async(member_task.id, current_user.id, valid_members.map(&:user_id))
end
- def areas_of_focus
- params[:areas_of_focus] || []
- end
-
def user_limit
limit = params.fetch(:limit, DEFAULT_INVITE_LIMIT)
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
index 9fb0fb734f9..892ef730884 100644
--- a/app/views/devise/confirmations/almost_there.haml
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -1,6 +1,8 @@
- user_email = "(#{params[:email]})" if params[:email].present?
- request_link_start = '<a href="%{new_user_confirmation_path}">'.html_safe % { new_user_confirmation_path: new_user_confirmation_path }
- request_link_end = '</a>'.html_safe
+- content_for :page_specific_javascripts do
+ = render "layouts/one_trust"
.well-confirmation.gl-text-center.gl-mb-6
%h1.gl-mt-0
diff --git a/config/feature_flags/experiment/member_areas_of_focus.yml b/config/feature_flags/experiment/member_areas_of_focus.yml
deleted file mode 100644
index e728ee7e3d3..00000000000
--- a/config/feature_flags/experiment/member_areas_of_focus.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: member_areas_of_focus
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65273
-rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/406
-milestone: '14.2'
-type: experiment
-group: group::expansion
-default_enabled: false
diff --git a/config/initializers/wikicloth_patch.rb b/config/initializers/wikicloth_patch.rb
new file mode 100644
index 00000000000..c033d9ad7ca
--- /dev/null
+++ b/config/initializers/wikicloth_patch.rb
@@ -0,0 +1,159 @@
+# frozen_string_literal: true
+
+require 'wikicloth'
+require 'wikicloth/wiki_buffer/var'
+
+# Adds patch for changes in this PR: https://github.com/nricciar/wikicloth/pull/112/files
+#
+# That fix has already been merged, but the maintainers are not releasing new versions, so we
+# need to patch it here.
+#
+# If they ever do release a version, then we can remove this file.
+#
+# See: https://gitlab.com/gitlab-org/gitlab/-/issues/334056#note_745336618
+
+# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
+raise 'New version of WikiCloth detected, please remove this patch' unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
+
+# rubocop:disable Style/ClassAndModuleChildren
+# rubocop:disable Layout/SpaceAroundEqualsInParameterDefault
+# rubocop:disable Style/HashSyntax
+# rubocop:disable Layout/SpaceAfterComma
+# rubocop:disable Style/RescueStandardError
+# rubocop:disable Rails/Output
+# rubocop:disable Style/MethodCallWithoutArgsParentheses
+# rubocop:disable Layout/EmptyLinesAroundClassBody
+# rubocop:disable Metrics/AbcSize
+# rubocop:disable Metrics/CyclomaticComplexity
+# rubocop:disable Metrics/PerceivedComplexity
+# rubocop:disable Cop/LineBreakAroundConditionalBlock
+# rubocop:disable Layout/EmptyLineAfterGuardClause
+# rubocop:disable Performance/ReverseEach
+# rubocop:disable Style/BlockDelimiters
+# rubocop:disable Cop/LineBreakAroundConditionalBlock
+# rubocop:disable Layout/MultilineBlockLayout
+# rubocop:disable Layout/BlockEndNewline
+module WikiCloth
+ class WikiCloth
+ def render(opt={})
+ self.options = { :noedit => false, :locale => I18n.default_locale, :fast => true, :output => :html, :link_handler => self.link_handler,
+ :params => self.params, :sections => self.sections }.merge(self.options).merge(opt)
+ self.options[:link_handler].params = options[:params]
+
+ I18n.locale = self.options[:locale]
+
+ data = self.sections.collect { |s| s.render(self.options) }.join
+
+ # This is the first patched line from:
+ # https://github.com/nricciar/wikicloth/pull/112/files#diff-eed3de11b953105f9181a6859d58f52af8912d28525fd2a289f8be184e66f531R69
+ data.gsub!(/<!--.*?-->/m,"")
+
+ data << "\n" if data.last(1) != "\n"
+ data << "garbage"
+
+ buffer = WikiBuffer.new("",options)
+
+ begin
+ if self.options[:fast]
+ until data.empty?
+ case data
+ when /\A\w+/
+ data = $'
+ @current_row += $&.length
+ buffer.add_word($&)
+ when /\A[^\w]+(\w|)/m
+ data = $'
+ $&.each_char { |c| add_current_char(buffer,c) }
+ end
+ end
+ else
+ data.each_char { |c| add_current_char(buffer,c) }
+ end
+ rescue => err
+ debug_tree = buffer.buffers.collect { |b| b.debug }.join("-->")
+ puts I18n.t("unknown error on line", :line => @current_line, :row => @current_row, :tree => debug_tree)
+ raise err
+ end
+
+ buffer.eof()
+ buffer.send("to_#{self.options[:output]}")
+ end
+
+ end
+
+ class WikiBuffer::Var < WikiBuffer
+ def to_html
+ return "" if will_not_be_rendered
+
+ if self.is_function?
+ if Extension.function_exists?(function_name)
+ return Extension.functions[function_name][:klass].new(@options).instance_exec( params.collect { |p| p.strip }, &Extension.functions[function_name][:block] ).to_s
+ end
+ ret = default_functions(function_name,params.collect { |p| p.strip })
+ ret ||= @options[:link_handler].function(function_name, params.collect { |p| p.strip })
+ ret.to_s
+ elsif self.is_param?
+ ret = nil
+ @options[:buffer].buffers.reverse.each do |b|
+ ret = b.get_param(params[0],params[1]) if b.instance_of?(WikiBuffer::HTMLElement) && b.element_name == "template"
+ break unless ret.nil?
+ end
+ ret.to_s
+ else
+ # put template at beginning of buffer
+ template_stack = @options[:buffer].buffers.collect { |b| b.get_param("__name") if b.instance_of?(WikiBuffer::HTMLElement) &&
+ b.element_name == "template" }.compact
+ if template_stack.last == params[0]
+ debug_tree = @options[:buffer].buffers.collect { |b| b.debug }.join("-->")
+ "<span class=\"error\">#{I18n.t('template loop detected', :tree => debug_tree)}</span>"
+ else
+ key = params[0].to_s.strip
+ key_options = params[1..-1].collect { |p| p.is_a?(Hash) ? { :name => p[:name].strip, :value => p[:value].strip } : p.strip }
+ key_options ||= []
+ key_digest = Digest::MD5.hexdigest(key_options.to_a.sort {|x,y| (x.is_a?(Hash) ? x[:name] : x) <=> (y.is_a?(Hash) ? y[:name] : y) }.inspect)
+
+ return @options[:params][key] if @options[:params].has_key?(key)
+ # if we have a valid cache fragment use it
+ return @options[:cache][key][key_digest] unless @options[:cache].nil? || @options[:cache][key].nil? || @options[:cache][key][key_digest].nil?
+
+ ret = @options[:link_handler].include_resource(key,key_options).to_s
+
+ # This is the second patched line from:
+ # https://github.com/nricciar/wikicloth/pull/112/files#diff-f262faf4fadb222cca87185be0fb65b3f49659abc840794cc83a736d41310fb1R83
+ ret.gsub!(/<!--.*?-->/m,"") unless ret.frozen?
+
+ count = 0
+ tag_attr = key_options.collect { |p|
+ if p.instance_of?(Hash)
+ "#{p[:name]}=\"#{p[:value].gsub(/"/,'&quot;')}\""
+ else
+ count += 1
+ "#{count}=\"#{p.gsub(/"/,'&quot;')}\""
+ end
+ }.join(" ")
+
+ self.data = ret.blank? ? "" : "<template __name=\"#{key}\" __hash=\"#{key_digest}\" #{tag_attr}>#{ret}</template>"
+ ""
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Style/ClassAndModuleChildren
+# rubocop:enable Layout/SpaceAroundEqualsInParameterDefault
+# rubocop:enable Style/HashSyntax
+# rubocop:enable Layout/SpaceAfterComma
+# rubocop:enable Style/RescueStandardError
+# rubocop:enable Rails/Output
+# rubocop:enable Style/MethodCallWithoutArgsParentheses
+# rubocop:enable Layout/EmptyLinesAroundClassBody
+# rubocop:enable Metrics/AbcSize
+# rubocop:enable Metrics/CyclomaticComplexity
+# rubocop:enable Metrics/PerceivedComplexity
+# rubocop:enable Cop/LineBreakAroundConditionalBlock
+# rubocop:enable Layout/EmptyLineAfterGuardClause
+# rubocop:enable Performance/ReverseEach
+# rubocop:enable Style/BlockDelimiters
+# rubocop:enable Cop/LineBreakAroundConditionalBlock
+# rubocop:enable Layout/MultilineBlockLayout
+# rubocop:enable Layout/BlockEndNewline
diff --git a/db/migrate/20211129151155_add_migrated_to_new_structure_column_to_vulnerability_occurrences.rb b/db/migrate/20211129151155_add_migrated_to_new_structure_column_to_vulnerability_occurrences.rb
new file mode 100644
index 00000000000..8c3cf82d7c7
--- /dev/null
+++ b/db/migrate/20211129151155_add_migrated_to_new_structure_column_to_vulnerability_occurrences.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddMigratedToNewStructureColumnToVulnerabilityOccurrences < Gitlab::Database::Migration[1.0]
+ def change
+ add_column :vulnerability_occurrences, :migrated_to_new_structure, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20211129151832_add_index_on_vulnerability_occurrences_migrated_to_new_structure_column.rb b/db/migrate/20211129151832_add_index_on_vulnerability_occurrences_migrated_to_new_structure_column.rb
new file mode 100644
index 00000000000..4cf8263f8f0
--- /dev/null
+++ b/db/migrate/20211129151832_add_index_on_vulnerability_occurrences_migrated_to_new_structure_column.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddIndexOnVulnerabilityOccurrencesMigratedToNewStructureColumn < Gitlab::Database::Migration[1.0]
+ INDEX_NAME = 'index_vulnerability_occurrences_on_migrated_to_new_structure'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :vulnerability_occurrences, [:migrated_to_new_structure, :id], name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :vulnerability_occurrences, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20211201101541_drop_clusters_applications_runners_ci_runners_fk.rb b/db/post_migrate/20211201101541_drop_clusters_applications_runners_ci_runners_fk.rb
new file mode 100644
index 00000000000..9a02f64e350
--- /dev/null
+++ b/db/post_migrate/20211201101541_drop_clusters_applications_runners_ci_runners_fk.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class DropClustersApplicationsRunnersCiRunnersFk < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ def up
+ with_lock_retries do
+ remove_foreign_key_if_exists(:clusters_applications_runners, :ci_runners, name: 'fk_02de2ded36')
+ end
+ end
+
+ def down
+ add_concurrent_foreign_key(:clusters_applications_runners, :ci_runners, name: 'fk_02de2ded36', column: :runner_id, target_column: :id, on_delete: 'set null')
+ end
+end
diff --git a/db/schema_migrations/20211129151155 b/db/schema_migrations/20211129151155
new file mode 100644
index 00000000000..4aa3e56bae6
--- /dev/null
+++ b/db/schema_migrations/20211129151155
@@ -0,0 +1 @@
+c1ba97f01fca6330628090010abb54220c0d057514386c6bb867c1b6f13f252c \ No newline at end of file
diff --git a/db/schema_migrations/20211129151832 b/db/schema_migrations/20211129151832
new file mode 100644
index 00000000000..fdfc464d136
--- /dev/null
+++ b/db/schema_migrations/20211129151832
@@ -0,0 +1 @@
+c6d257f635049f88cd6efba903c9384a0a1af23b3c8fe6fa7f0842dcdf9f7e39 \ No newline at end of file
diff --git a/db/schema_migrations/20211201101541 b/db/schema_migrations/20211201101541
new file mode 100644
index 00000000000..52f43ddcd2f
--- /dev/null
+++ b/db/schema_migrations/20211201101541
@@ -0,0 +1 @@
+277cfcd1002e32c6cd664d6c0b6a7cbdf2ed7e5242e46dbddc4f99b0e8422361 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index c431c35af89..d57b66ad602 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20828,6 +20828,7 @@ CREATE TABLE vulnerability_occurrences (
cve text,
location jsonb,
detection_method smallint DEFAULT 0 NOT NULL,
+ migrated_to_new_structure boolean DEFAULT false NOT NULL,
CONSTRAINT check_4a3a60f2ba CHECK ((char_length(solution) <= 7000)),
CONSTRAINT check_ade261da6b CHECK ((char_length(description) <= 15000)),
CONSTRAINT check_df6dd20219 CHECK ((char_length(message) <= 3000)),
@@ -27780,6 +27781,8 @@ CREATE INDEX index_vulnerability_occurrences_on_location_cluster_id ON vulnerabi
CREATE INDEX index_vulnerability_occurrences_on_location_image ON vulnerability_occurrences USING gin (((location -> 'image'::text))) WHERE (report_type = ANY (ARRAY[2, 7]));
+CREATE INDEX index_vulnerability_occurrences_on_migrated_to_new_structure ON vulnerability_occurrences USING btree (migrated_to_new_structure, id);
+
CREATE INDEX index_vulnerability_occurrences_on_primary_identifier_id ON vulnerability_occurrences USING btree (primary_identifier_id);
CREATE INDEX index_vulnerability_occurrences_on_project_fingerprint ON vulnerability_occurrences USING btree (project_fingerprint);
@@ -28891,9 +28894,6 @@ ALTER TABLE ONLY deployments
ALTER TABLE ONLY epics
ADD CONSTRAINT fk_013c9f36ca FOREIGN KEY (due_date_sourcing_epic_id) REFERENCES epics(id) ON DELETE SET NULL;
-ALTER TABLE ONLY clusters_applications_runners
- ADD CONSTRAINT fk_02de2ded36 FOREIGN KEY (runner_id) REFERENCES ci_runners(id) ON DELETE SET NULL;
-
ALTER TABLE ONLY incident_management_escalation_rules
ADD CONSTRAINT fk_0314ee86eb FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index fb821824dd1..f590c6ff51c 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Geo Nodes API **(PREMIUM SELF)**
-To interact with Geo node endpoints, you need to authenticate yourself as an
+To interact with Geo node endpoints, you must authenticate yourself as an
administrator.
## Create a new Geo node
@@ -26,7 +26,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/
| Attribute | Type | Required | Description |
| ----------------------------| ------- | -------- | -----------------------------------------------------------------|
-| `primary` | boolean | no | Specifying whether this node will be primary. Defaults to false. |
+| `primary` | boolean | no | Specifying whether this node should be primary. Defaults to false. |
| `enabled` | boolean | no | Flag indicating if the Geo node is enabled. Defaults to true. |
| `name` | string | yes | The unique identifier for the Geo node. Must match `geo_node_name` if it is set in `gitlab.rb`, otherwise it must match `external_url` |
| `url` | string | yes | The user-facing URL for the Geo node. |
@@ -35,11 +35,11 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" "https://primary.example.com/
| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this secondary node. Defaults to 25. |
| `verification_max_capacity` | integer | no | Control the maximum concurrency of repository verification for this node. Defaults to 100. |
| `container_repositories_max_capacity` | integer | no | Control the maximum concurrency of container repository sync for this node. Defaults to 10. |
-| `sync_object_storage` | boolean | no | Flag indicating if the secondary Geo node will replicate blobs in Object Storage. Defaults to false. |
+| `sync_object_storage` | boolean | no | Flag indicating if the secondary Geo node should replicate blobs in Object Storage. Defaults to false. |
| `selective_sync_type` | string | no | Limit syncing to only specific groups or shards. Valid values: `"namespaces"`, `"shards"`, or `null`. |
| `selective_sync_shards` | array | no | The repository storage for the projects synced if `selective_sync_type` == `shards`. |
| `selective_sync_namespace_ids` | array | no | The IDs of groups that should be synced, if `selective_sync_type` == `namespaces`. |
-| `minimum_reverification_interval` | integer | no | The interval (in days) in which the repository verification is valid. Once expired, it will be reverified. This has no effect when set on a secondary node. |
+| `minimum_reverification_interval` | integer | no | The interval (in days) in which the repository verification is valid. Once expired, it is reverified. This has no effect when set on a secondary node. |
Example response:
@@ -199,11 +199,11 @@ PUT /geo_nodes/:id
| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this secondary node. |
| `verification_max_capacity` | integer | no | Control the maximum concurrency of verification for this node. |
| `container_repositories_max_capacity` | integer | no | Control the maximum concurrency of container repository sync for this node. |
-| `sync_object_storage` | boolean | no | Flag indicating if the secondary Geo node will replicate blobs in Object Storage. |
+| `sync_object_storage` | boolean | no | Flag indicating if the secondary Geo node should replicate blobs in Object Storage. |
| `selective_sync_type` | string | no | Limit syncing to only specific groups or shards. Valid values: `"namespaces"`, `"shards"`, or `null`. |
| `selective_sync_shards` | array | no | The repository storage for the projects synced if `selective_sync_type` == `shards`. |
| `selective_sync_namespace_ids` | array | no | The IDs of groups that should be synced, if `selective_sync_type` == `namespaces`. |
-| `minimum_reverification_interval` | integer | no | The interval (in days) in which the repository verification is valid. Once expired, it will be reverified. This has no effect when set on a secondary node. |
+| `minimum_reverification_interval` | integer | no | The interval (in days) in which the repository verification is valid. Once expired, it is reverified. This has no effect when set on a secondary node. |
Example response:
@@ -241,7 +241,7 @@ Example response:
Removes the Geo node.
NOTE:
-Only a Geo primary node will accept this request.
+Only a Geo primary node accepts this request.
```plaintext
DELETE /geo_nodes/:id
diff --git a/doc/api/invitations.md b/doc/api/invitations.md
index 7ba59509f63..0bf9d106404 100644
--- a/doc/api/invitations.md
+++ b/doc/api/invitations.md
@@ -42,7 +42,6 @@ POST /projects/:id/invitations
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format YEAR-MONTH-DAY |
| `invite_source` | string | no | The source of the invitation that starts the member creation process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327120). |
-| `areas_of_focus` | string | no | Areas the inviter wants the member to focus upon. |
| `tasks_to_be_done` | array of strings | no | Tasks the inviter wants the member to focus on. The tasks are added as issues to a specified project. The possible values are: `ci`, `code` and `issues`. If specified, requires `tasks_project_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.6 |
| `tasks_project_id` | integer | no | The project ID in which to create the task issues. If specified, requires `tasks_to_be_done`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.6 |
diff --git a/doc/api/members.md b/doc/api/members.md
index 48ac491dfe4..51f7b74f110 100644
--- a/doc/api/members.md
+++ b/doc/api/members.md
@@ -429,7 +429,6 @@ POST /projects/:id/members
| `access_level` | integer | yes | A valid access level |
| `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY` |
| `invite_source` | string | no | The source of the invitation that starts the member creation process. See [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327120). |
-| `areas_of_focus` | string | no | Areas the inviter wants the member to focus upon. |
| `tasks_to_be_done` | array of strings | no | Tasks the inviter wants the member to focus on. The tasks are added as issues to a specified project. The possible values are: `ci`, `code` and `issues`. If specified, requires `tasks_project_id`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
| `tasks_project_id` | integer | no | The project ID in which to create the task issues. If specified, requires `tasks_to_be_done`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69299) in GitLab 14.5 [with a flag](../administration/feature_flags.md) named `invite_members_for_task`. Disabled by default. |
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 316fd0dce16..47a2d215024 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -313,12 +313,8 @@ As in other list types, click the trash icon to remove a list.
### Iteration lists **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.11 [with a flag](../../administration/feature_flags.md) named `iteration_board_lists`. Enabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is available. To hide the feature, ask an
-administrator to [disable the `iteration_board_lists` flag](../../administration/feature_flags.md).
-On GitLab.com, this feature is available.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/250479) in GitLab 13.11 [with a flag](../../administration/feature_flags.md) named `iteration_board_lists`. Enabled by default.
+> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75404) in GitLab 14.6. Feature flag `iteration_board_lists` removed.
You're also able to create lists of an iteration.
These lists filter issues by the assigned iteration.
diff --git a/lib/api/invitations.rb b/lib/api/invitations.rb
index f7f5af07378..d78576b5d5b 100644
--- a/lib/api/invitations.rb
+++ b/lib/api/invitations.rb
@@ -24,7 +24,6 @@ module API
requires :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'invitations-api'
- optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
diff --git a/lib/api/members.rb b/lib/api/members.rb
index 13c8e60aa6d..4798edc4ddf 100644
--- a/lib/api/members.rb
+++ b/lib/api/members.rb
@@ -95,7 +95,6 @@ module API
requires :user_id, types: [Integer, String], desc: 'The user ID of the new member or multiple IDs separated by commas.'
optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY'
optional :invite_source, type: String, desc: 'Source that triggered the member creation process', default: 'members-api'
- optional :areas_of_focus, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Areas the inviter wants the member to focus upon'
optional :tasks_to_be_done, type: Array[String], coerce_with: Validations::Types::CommaSeparatedToArray.coerce, desc: 'Tasks the inviter wants the member to do'
optional :tasks_project_id, type: Integer, desc: 'The project ID in which to create the task issues'
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 218d2d41aa9..d16a6726157 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -873,6 +873,9 @@ msgstr ""
msgid "%{scope} results for term '%{term}'"
msgstr ""
+msgid "%{search} %{description} %{scope}"
+msgstr ""
+
msgid "%{seconds}s"
msgstr ""
@@ -16077,6 +16080,51 @@ msgstr ""
msgid "Global notification settings"
msgstr ""
+msgid "GlobalSearch|%{count} default results provided. Use the up and down arrow keys to navigate search results list."
+msgstr ""
+
+msgid "GlobalSearch|Issues I've created"
+msgstr ""
+
+msgid "GlobalSearch|Issues assigned to me"
+msgstr ""
+
+msgid "GlobalSearch|Merge requests I've created"
+msgstr ""
+
+msgid "GlobalSearch|Merge requests assigned to me"
+msgstr ""
+
+msgid "GlobalSearch|Merge requests that I'm a reviewer"
+msgstr ""
+
+msgid "GlobalSearch|Results updated. %{count} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit."
+msgstr ""
+
+msgid "GlobalSearch|Search GitLab"
+msgstr ""
+
+msgid "GlobalSearch|Search or jump to..."
+msgstr ""
+
+msgid "GlobalSearch|Search results are loading"
+msgstr ""
+
+msgid "GlobalSearch|Type and press the enter key to submit search."
+msgstr ""
+
+msgid "GlobalSearch|Type for new suggestions to appear below."
+msgstr ""
+
+msgid "GlobalSearch|in all GitLab"
+msgstr ""
+
+msgid "GlobalSearch|in group"
+msgstr ""
+
+msgid "GlobalSearch|in project"
+msgstr ""
+
msgid "Go Back"
msgstr ""
@@ -18843,6 +18891,9 @@ msgstr ""
msgid "Integrations|can't exceed %{recipients_limit}"
msgstr ""
+msgid "Interactive developer security education."
+msgstr ""
+
msgid "Interactive mode"
msgstr ""
@@ -19071,21 +19122,9 @@ msgstr ""
msgid "InviteMembersModal|Close invite team members"
msgstr ""
-msgid "InviteMembersModal|Collaborate on open issues and merge requests"
-msgstr ""
-
-msgid "InviteMembersModal|Configure CI/CD"
-msgstr ""
-
-msgid "InviteMembersModal|Configure security features"
-msgstr ""
-
msgid "InviteMembersModal|Congratulations on creating your project, you're almost there!"
msgstr ""
-msgid "InviteMembersModal|Contribute to the codebase"
-msgstr ""
-
msgid "InviteMembersModal|Create issues for your new team member to work on (optional)"
msgstr ""
@@ -19110,9 +19149,6 @@ msgstr ""
msgid "InviteMembersModal|Members were successfully added"
msgstr ""
-msgid "InviteMembersModal|Other"
-msgstr ""
-
msgid "InviteMembersModal|Search for a group to invite"
msgstr ""
@@ -19131,9 +19167,6 @@ msgstr ""
msgid "InviteMembersModal|To assign issues to a new team member, you need a project for the issues. %{linkStart}Create a project to get started.%{linkEnd}"
msgstr ""
-msgid "InviteMembersModal|What would you like new member(s) to focus on? (optional)"
-msgstr ""
-
msgid "InviteMembersModal|You're inviting a group to the %{strongStart}%{name}%{strongEnd} group."
msgstr ""
@@ -19455,9 +19488,6 @@ msgstr ""
msgid "Issues"
msgstr ""
-msgid "Issues I've created"
-msgstr ""
-
msgid "Issues Rate Limits"
msgstr ""
@@ -19467,9 +19497,6 @@ msgstr ""
msgid "Issues are being rebalanced at the moment, so manual reordering is disabled."
msgstr ""
-msgid "Issues assigned to me"
-msgstr ""
-
msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable."
msgstr ""
@@ -20196,6 +20223,9 @@ msgstr ""
msgid "Ki"
msgstr ""
+msgid "Kontra"
+msgstr ""
+
msgid "Kroki"
msgstr ""
@@ -21859,18 +21889,9 @@ msgstr ""
msgid "Merge requests"
msgstr ""
-msgid "Merge requests I've created"
-msgstr ""
-
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
-msgid "Merge requests assigned to me"
-msgstr ""
-
-msgid "Merge requests that I'm a reviewer"
-msgstr ""
-
msgid "Merge the branch and fix any conflicts that come up"
msgstr ""
@@ -30671,6 +30692,9 @@ msgstr ""
msgid "Secure token that identifies an external storage request."
msgstr ""
+msgid "SecureCodeWarrior"
+msgstr ""
+
msgid "Security"
msgstr ""
@@ -30695,6 +30719,9 @@ msgstr ""
msgid "Security report is out of date. Run %{newPipelineLinkStart}a new pipeline%{newPipelineLinkEnd} for the target branch (%{targetBranchName})"
msgstr ""
+msgid "Security training with guide and learning pathways."
+msgstr ""
+
msgid "SecurityApprovals|A merge request approval is required when a security report contains a new vulnerability."
msgstr ""
@@ -36606,6 +36633,9 @@ msgstr ""
msgid "Track your GitLab projects with GitLab for Slack."
msgstr ""
+msgid "Training mode"
+msgstr ""
+
msgid "Transfer"
msgstr ""
@@ -41231,18 +41261,9 @@ msgstr ""
msgid "in"
msgstr ""
-msgid "in all GitLab"
-msgstr ""
-
-msgid "in group"
-msgstr ""
-
msgid "in group %{link_to_group}"
msgstr ""
-msgid "in project"
-msgstr ""
-
msgid "in project %{link_to_project}"
msgstr ""
diff --git a/spec/controllers/repositories/git_http_controller_spec.rb b/spec/controllers/repositories/git_http_controller_spec.rb
index b5cd14154a3..4a6e745cd63 100644
--- a/spec/controllers/repositories/git_http_controller_spec.rb
+++ b/spec/controllers/repositories/git_http_controller_spec.rb
@@ -90,6 +90,14 @@ RSpec.describe Repositories::GitHttpController do
end
end
end
+
+ context 'when the user is a deploy token' do
+ it_behaves_like Repositories::GitHttpController do
+ let(:container) { project }
+ let(:user) { create(:deploy_token, :project, projects: [project]) }
+ let(:access_checker_class) { Gitlab::GitAccess }
+ end
+ end
end
context 'when repository container is a project wiki' do
diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb
index 1cae7cdeb16..0ce50107e54 100644
--- a/spec/features/groups/members/manage_members_spec.rb
+++ b/spec/features/groups/members/manage_members_spec.rb
@@ -85,33 +85,6 @@ RSpec.describe 'Groups > Members > Manage members' do
property: 'existing_user',
user: user1
)
- expect_no_snowplow_event(
- category: 'Members::CreateService',
- action: 'area_of_focus'
- )
- end
-
- it 'adds a user to group with area_of_focus', :js, :snowplow, :aggregate_failures do
- stub_experiments(member_areas_of_focus: :candidate)
- group.add_owner(user1)
-
- visit group_group_members_path(group)
-
- invite_member(user2.name, role: 'Reporter', area_of_focus: true)
- wait_for_requests
-
- expect_snowplow_event(
- category: 'Members::CreateService',
- action: 'area_of_focus',
- label: 'Contribute to the codebase',
- property: group.members.last.id.to_s
- )
- expect_snowplow_event(
- category: 'Members::CreateService',
- action: 'area_of_focus',
- label: 'Collaborate on open issues and merge requests',
- property: group.members.last.id.to_s
- )
end
it 'do not disclose email addresses', :js do
@@ -221,36 +194,9 @@ RSpec.describe 'Groups > Members > Manage members' do
property: 'net_new_user',
user: user1
)
- expect_no_snowplow_event(
- category: 'Members::CreateService',
- action: 'area_of_focus'
- )
end
end
- it 'invite user to group with area_of_focus', :js, :snowplow, :aggregate_failures do
- stub_experiments(member_areas_of_focus: :candidate)
- group.add_owner(user1)
-
- visit group_group_members_path(group)
-
- invite_member('test@example.com', role: 'Reporter', area_of_focus: true)
- wait_for_requests
-
- expect_snowplow_event(
- category: 'Members::InviteService',
- action: 'area_of_focus',
- label: 'Contribute to the codebase',
- property: group.members.last.id.to_s
- )
- expect_snowplow_event(
- category: 'Members::InviteService',
- action: 'area_of_focus',
- label: 'Collaborate on open issues and merge requests',
- property: group.members.last.id.to_s
- )
- end
-
context 'when user is a guest' do
before do
group.add_guest(user1)
diff --git a/spec/features/one_trust_spec.rb b/spec/features/one_trust_spec.rb
new file mode 100644
index 00000000000..0ed08e8b99b
--- /dev/null
+++ b/spec/features/one_trust_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'OneTrust' do
+ context 'almost there page' do
+ context 'when OneTrust is enabled' do
+ let_it_be(:onetrust_url) { 'https://*.onetrust.com' }
+ let_it_be(:one_trust_id) { SecureRandom.uuid }
+
+ before do
+ stub_config(extra: { one_trust_id: one_trust_id })
+ stub_feature_flags(ecomm_instrumentation: true)
+ visit users_almost_there_path
+ end
+
+ it 'has the OneTrust CSP settings', :aggregate_failures do
+ expect(response_headers['Content-Security-Policy']).to include("#{onetrust_url}")
+ expect(page.html).to include("https://cdn.cookielaw.org/consent/#{one_trust_id}/OtAutoBlock.js")
+ end
+ end
+ end
+end
diff --git a/spec/frontend/header_search/components/app_spec.js b/spec/frontend/header_search/components/app_spec.js
index 7ecbef8af4b..194846c410a 100644
--- a/spec/frontend/header_search/components/app_spec.js
+++ b/spec/frontend/header_search/components/app_spec.js
@@ -6,6 +6,7 @@ import HeaderSearchApp from '~/header_search/components/app.vue';
import HeaderSearchAutocompleteItems from '~/header_search/components/header_search_autocomplete_items.vue';
import HeaderSearchDefaultItems from '~/header_search/components/header_search_default_items.vue';
import HeaderSearchScopedItems from '~/header_search/components/header_search_scoped_items.vue';
+import { SEARCH_INPUT_DESCRIPTION, SEARCH_RESULTS_DESCRIPTION } from '~/header_search/constants';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import { ENTER_KEY } from '~/lib/utils/keys';
import { visitUrl } from '~/lib/utils/url_utility';
@@ -14,6 +15,7 @@ import {
MOCK_SEARCH_QUERY,
MOCK_USERNAME,
MOCK_DEFAULT_SEARCH_OPTIONS,
+ MOCK_SCOPED_SEARCH_OPTIONS,
} from '../mock_data';
Vue.use(Vuex);
@@ -59,11 +61,26 @@ describe('HeaderSearchApp', () => {
const findHeaderSearchAutocompleteItems = () =>
wrapper.findComponent(HeaderSearchAutocompleteItems);
const findDropdownKeyboardNavigation = () => wrapper.findComponent(DropdownKeyboardNavigation);
+ const findSearchInputDescription = () => wrapper.find(`#${SEARCH_INPUT_DESCRIPTION}`);
+ const findSearchResultsDescription = () => wrapper.findByTestId(SEARCH_RESULTS_DESCRIPTION);
describe('template', () => {
- it('always renders Header Search Input', () => {
- createComponent();
- expect(findHeaderSearchInput().exists()).toBe(true);
+ describe('always renders', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('Header Search Input', () => {
+ expect(findHeaderSearchInput().exists()).toBe(true);
+ });
+
+ it('Search Input Description', () => {
+ expect(findSearchInputDescription().exists()).toBe(true);
+ });
+
+ it('Search Results Description', () => {
+ expect(findSearchResultsDescription().exists()).toBe(true);
+ });
});
describe.each`
@@ -77,7 +94,7 @@ describe('HeaderSearchApp', () => {
beforeEach(() => {
window.gon.current_username = username;
createComponent();
- wrapper.setData({ showDropdown });
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
});
it(`should${showSearchDropdown ? '' : ' not'} render`, () => {
@@ -123,6 +140,53 @@ describe('HeaderSearchApp', () => {
});
},
);
+
+ describe.each`
+ username | showDropdown | expectedDesc
+ ${null} | ${false} | ${HeaderSearchApp.i18n.searchInputDescribeByNoDropdown}
+ ${null} | ${true} | ${HeaderSearchApp.i18n.searchInputDescribeByNoDropdown}
+ ${MOCK_USERNAME} | ${false} | ${HeaderSearchApp.i18n.searchInputDescribeByWithDropdown}
+ ${MOCK_USERNAME} | ${true} | ${HeaderSearchApp.i18n.searchInputDescribeByWithDropdown}
+ `('Search Input Description', ({ username, showDropdown, expectedDesc }) => {
+ describe(`current_username is ${username} and showDropdown is ${showDropdown}`, () => {
+ beforeEach(() => {
+ window.gon.current_username = username;
+ createComponent();
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
+ });
+
+ it(`sets description to ${expectedDesc}`, () => {
+ expect(findSearchInputDescription().text()).toBe(expectedDesc);
+ });
+ });
+ });
+
+ describe.each`
+ username | showDropdown | search | loading | searchOptions | expectedDesc
+ ${null} | ${true} | ${''} | ${false} | ${[]} | ${''}
+ ${MOCK_USERNAME} | ${false} | ${''} | ${false} | ${[]} | ${''}
+ ${MOCK_USERNAME} | ${true} | ${''} | ${false} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
+ ${MOCK_USERNAME} | ${true} | ${''} | ${true} | ${MOCK_DEFAULT_SEARCH_OPTIONS} | ${`${MOCK_DEFAULT_SEARCH_OPTIONS.length} default results provided. Use the up and down arrow keys to navigate search results list.`}
+ ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${false} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${`Results updated. ${MOCK_SCOPED_SEARCH_OPTIONS.length} results available. Use the up and down arrow keys to navigate search results list, or ENTER to submit.`}
+ ${MOCK_USERNAME} | ${true} | ${MOCK_SEARCH} | ${true} | ${MOCK_SCOPED_SEARCH_OPTIONS} | ${HeaderSearchApp.i18n.searchResultsLoading}
+ `(
+ 'Search Results Description',
+ ({ username, showDropdown, search, loading, searchOptions, expectedDesc }) => {
+ describe(`search is ${search}, loading is ${loading}, and showSearchDropdown is ${
+ Boolean(username) && showDropdown
+ }`, () => {
+ beforeEach(() => {
+ window.gon.current_username = username;
+ createComponent({ search, loading }, { searchOptions: () => searchOptions });
+ findHeaderSearchInput().vm.$emit(showDropdown ? 'click' : '');
+ });
+
+ it(`sets description to ${expectedDesc}`, () => {
+ expect(findSearchResultsDescription().text()).toBe(expectedDesc);
+ });
+ });
+ },
+ );
});
describe('events', () => {
diff --git a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
index ecfaa8e9443..bec0cbc8a5c 100644
--- a/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_autocomplete_items_spec.js
@@ -110,11 +110,11 @@ describe('HeaderSearchAutocompleteItems', () => {
});
describe.each`
- currentFocusedOption | isFocused
- ${null} | ${false}
- ${{ html_id: 'not-a-match' }} | ${false}
- ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0]} | ${true}
- `('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_SORTED_AUTOCOMPLETE_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, {}, { currentFocusedOption });
@@ -123,6 +123,10 @@ describe('HeaderSearchAutocompleteItems', () => {
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
});
});
});
diff --git a/spec/frontend/header_search/components/header_search_default_items_spec.js b/spec/frontend/header_search/components/header_search_default_items_spec.js
index afad9b5b138..abcacc487df 100644
--- a/spec/frontend/header_search/components/header_search_default_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_default_items_spec.js
@@ -83,11 +83,11 @@ describe('HeaderSearchDefaultItems', () => {
});
describe.each`
- currentFocusedOption | isFocused
- ${null} | ${false}
- ${{ html_id: 'not-a-match' }} | ${false}
- ${MOCK_DEFAULT_SEARCH_OPTIONS[0]} | ${true}
- `('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_DEFAULT_SEARCH_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, { currentFocusedOption });
@@ -96,6 +96,10 @@ describe('HeaderSearchDefaultItems', () => {
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
});
});
});
diff --git a/spec/frontend/header_search/components/header_search_scoped_items_spec.js b/spec/frontend/header_search/components/header_search_scoped_items_spec.js
index 2fdd03fdae3..a65b4d8b813 100644
--- a/spec/frontend/header_search/components/header_search_scoped_items_spec.js
+++ b/spec/frontend/header_search/components/header_search_scoped_items_spec.js
@@ -37,6 +37,8 @@ describe('HeaderSearchScopedItems', () => {
const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
const findFirstDropdownItem = () => findDropdownItems().at(0);
const findDropdownItemTitles = () => findDropdownItems().wrappers.map((w) => trimText(w.text()));
+ const findDropdownItemAriaLabels = () =>
+ findDropdownItems().wrappers.map((w) => trimText(w.attributes('aria-label')));
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
describe('template', () => {
@@ -56,6 +58,13 @@ describe('HeaderSearchScopedItems', () => {
expect(findDropdownItemTitles()).toStrictEqual(expectedTitles);
});
+ it('renders aria-labels correctly', () => {
+ const expectedLabels = MOCK_SCOPED_SEARCH_OPTIONS.map((o) =>
+ trimText(`${MOCK_SEARCH} ${o.description} ${o.scope || ''}`),
+ );
+ expect(findDropdownItemAriaLabels()).toStrictEqual(expectedLabels);
+ });
+
it('renders links correctly', () => {
const expectedLinks = MOCK_SCOPED_SEARCH_OPTIONS.map((o) => o.url);
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
@@ -63,11 +72,11 @@ describe('HeaderSearchScopedItems', () => {
});
describe.each`
- currentFocusedOption | isFocused
- ${null} | ${false}
- ${{ html_id: 'not-a-match' }} | ${false}
- ${MOCK_SCOPED_SEARCH_OPTIONS[0]} | ${true}
- `('isOptionFocused', ({ currentFocusedOption, isFocused }) => {
+ currentFocusedOption | isFocused | ariaSelected
+ ${null} | ${false} | ${undefined}
+ ${{ html_id: 'not-a-match' }} | ${false} | ${undefined}
+ ${MOCK_SCOPED_SEARCH_OPTIONS[0]} | ${true} | ${'true'}
+ `('isOptionFocused', ({ currentFocusedOption, isFocused, ariaSelected }) => {
describe(`when currentFocusedOption.html_id is ${currentFocusedOption?.html_id}`, () => {
beforeEach(() => {
createComponent({}, { currentFocusedOption });
@@ -76,6 +85,10 @@ describe('HeaderSearchScopedItems', () => {
it(`should${isFocused ? '' : ' not'} have gl-bg-gray-50 applied`, () => {
expect(findFirstDropdownItem().classes('gl-bg-gray-50')).toBe(isFocused);
});
+
+ it(`sets "aria-selected to ${ariaSelected}`, () => {
+ expect(findFirstDropdownItem().attributes('aria-selected')).toBe(ariaSelected);
+ });
});
});
});
diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js
index 24df424b40b..f3cad3fc066 100644
--- a/spec/frontend/invite_members/components/invite_members_modal_spec.js
+++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js
@@ -6,7 +6,6 @@ import {
GlSprintf,
GlLink,
GlModal,
- GlFormCheckboxGroup,
} from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import { stubComponent } from 'helpers/stub_component';
@@ -19,7 +18,6 @@ import ModalConfetti from '~/invite_members/components/confetti.vue';
import MembersTokenSelect from '~/invite_members/components/members_token_select.vue';
import {
INVITE_MEMBERS_IN_COMMENT,
- MEMBER_AREAS_OF_FOCUS,
INVITE_MEMBERS_FOR_TASK,
CANCEL_BUTTON_TEXT,
INVITE_BUTTON_TEXT,
@@ -52,12 +50,7 @@ const inviteeType = 'members';
const accessLevels = { Guest: 10, Reporter: 20, Developer: 30, Maintainer: 40, Owner: 50 };
const defaultAccessLevel = 10;
const inviteSource = 'unknown';
-const noSelectionAreasOfFocus = ['no_selection'];
const helpLink = 'https://example.com';
-const areasOfFocusOptions = [
- { text: 'area1', value: 'area1' },
- { text: 'area2', value: 'area2' },
-];
const tasksToBeDoneOptions = [
{ text: 'First task', value: 'first' },
{ text: 'Second task', value: 'second' },
@@ -96,9 +89,7 @@ const createComponent = (data = {}, props = {}) => {
isProject,
inviteeType,
accessLevels,
- areasOfFocusOptions,
defaultAccessLevel,
- noSelectionAreasOfFocus,
tasksToBeDoneOptions,
projects,
helpLink,
@@ -164,7 +155,6 @@ describe('InviteMembersModal', () => {
const membersFormGroupInvalidFeedback = () => findMembersFormGroup().props('invalidFeedback');
const membersFormGroupDescription = () => findMembersFormGroup().props('description');
const findMembersSelect = () => wrapper.findComponent(MembersTokenSelect);
- const findAreaofFocusCheckBoxGroup = () => wrapper.findComponent(GlFormCheckboxGroup);
const findTasksToBeDone = () => wrapper.findByTestId('invite-members-modal-tasks-to-be-done');
const findTasks = () => wrapper.findByTestId('invite-members-modal-tasks');
const findProjectSelect = () => wrapper.findByTestId('invite-members-modal-project-select');
@@ -215,21 +205,6 @@ describe('InviteMembersModal', () => {
});
});
- describe('rendering the areas_of_focus', () => {
- it('renders the areas_of_focus checkboxes', () => {
- createComponent();
-
- expect(findAreaofFocusCheckBoxGroup().props('options')).toBe(areasOfFocusOptions);
- expect(findAreaofFocusCheckBoxGroup().exists()).toBe(true);
- });
-
- it('does not render the areas_of_focus checkboxes', () => {
- createComponent({}, { areasOfFocusOptions: [] });
-
- expect(findAreaofFocusCheckBoxGroup().exists()).toBe(false);
- });
- });
-
describe('rendering the tasks to be done', () => {
const setupComponent = (
extraData = {},
@@ -442,20 +417,6 @@ describe('InviteMembersModal', () => {
"The member's email address is not allowed for this project. Go to the Admin area > Sign-up restrictions, and check Allowed domains for sign-ups.";
const expectedSyntaxError = 'email contains an invalid email address';
- it('calls the API with the expected focus data when an areas_of_focus checkbox is clicked', () => {
- const spy = jest.spyOn(Api, 'addGroupMembersByUserId');
- const expectedFocus = [areasOfFocusOptions[0].value];
- createComponent({ newUsersToInvite: [user1] });
-
- findAreaofFocusCheckBoxGroup().vm.$emit('input', expectedFocus);
- clickInviteButton();
-
- expect(spy).toHaveBeenCalledWith(
- user1.id.toString(),
- expect.objectContaining({ areas_of_focus: expectedFocus }),
- );
- });
-
describe('when inviting an existing user to group by user ID', () => {
const postData = {
user_id: '1,2',
@@ -463,7 +424,6 @@ describe('InviteMembersModal', () => {
expires_at: undefined,
invite_source: inviteSource,
format: 'json',
- areas_of_focus: noSelectionAreasOfFocus,
tasks_to_be_done: [],
tasks_project_id: '',
};
@@ -476,16 +436,6 @@ describe('InviteMembersModal', () => {
jest.spyOn(Api, 'addGroupMembersByUserId').mockResolvedValue({ data: postData });
});
- it('includes the non-default selected areas of focus', () => {
- const focus = ['abc'];
- const updatedPostData = { ...postData, areas_of_focus: focus };
- wrapper.setData({ selectedAreasOfFocus: focus });
-
- clickInviteButton();
-
- expect(Api.addGroupMembersByUserId).toHaveBeenCalledWith(id, updatedPostData);
- });
-
describe('when triggered from regular mounting', () => {
beforeEach(() => {
clickInviteButton();
@@ -661,7 +611,6 @@ describe('InviteMembersModal', () => {
expires_at: undefined,
email: 'email@example.com',
invite_source: inviteSource,
- areas_of_focus: noSelectionAreasOfFocus,
tasks_to_be_done: [],
tasks_project_id: '',
format: 'json',
@@ -675,16 +624,6 @@ describe('InviteMembersModal', () => {
jest.spyOn(Api, 'inviteGroupMembersByEmail').mockResolvedValue({ data: postData });
});
- it('includes the non-default selected areas of focus', () => {
- const focus = ['abc'];
- const updatedPostData = { ...postData, areas_of_focus: focus };
- wrapper.setData({ selectedAreasOfFocus: focus });
-
- clickInviteButton();
-
- expect(Api.inviteGroupMembersByEmail).toHaveBeenCalledWith(id, updatedPostData);
- });
-
describe('when triggered from regular mounting', () => {
beforeEach(() => {
clickInviteButton();
@@ -792,7 +731,6 @@ describe('InviteMembersModal', () => {
access_level: defaultAccessLevel,
expires_at: undefined,
invite_source: inviteSource,
- areas_of_focus: noSelectionAreasOfFocus,
format: 'json',
tasks_to_be_done: [],
tasks_project_id: '',
@@ -951,30 +889,12 @@ describe('InviteMembersModal', () => {
expect(ExperimentTracking).not.toHaveBeenCalledWith(INVITE_MEMBERS_IN_COMMENT);
});
- it('tracks the view for areas_of_focus', () => {
- eventHub.$emit('openModal', { inviteeType: 'members' });
-
- expect(ExperimentTracking).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.name);
- expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.view);
- });
-
it('tracks the view for learn_gitlab source', () => {
eventHub.$emit('openModal', { inviteeType: 'members', source: LEARN_GITLAB });
expect(ExperimentTracking).toHaveBeenCalledWith(INVITE_MEMBERS_FOR_TASK.name);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(LEARN_GITLAB);
});
-
- it('tracks the invite for areas_of_focus', () => {
- eventHub.$emit('openModal', { inviteeType: 'members' });
-
- clickInviteButton();
-
- expect(ExperimentTracking).toHaveBeenCalledWith(MEMBER_AREAS_OF_FOCUS.name);
- expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
- MEMBER_AREAS_OF_FOCUS.submit,
- );
- });
});
});
});
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index 4e0ea6a9717..759844b941a 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -5,7 +5,10 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import SecurityConfigurationApp, { i18n } from '~/security_configuration/components/app.vue';
+import SecurityConfigurationApp, {
+ i18n,
+ TRAINING_PROVIDERS,
+} from '~/security_configuration/components/app.vue';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
import {
@@ -20,6 +23,7 @@ import {
AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
} from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
+import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import {
@@ -78,6 +82,7 @@ describe('App component', () => {
const findTabs = () => wrapper.findAllComponents(GlTab);
const findByTestId = (id) => wrapper.findByTestId(id);
const findFeatureCards = () => wrapper.findAllComponents(FeatureCard);
+ const findTrainingProviderList = () => wrapper.findComponent(TrainingProviderList);
const findManageViaMRErrorAlert = () => wrapper.findByTestId('manage-via-mr-error-alert');
const findLink = ({ href, text, container = wrapper }) => {
const selector = `a[href="${href}"]`;
@@ -180,6 +185,10 @@ describe('App component', () => {
expect(findComplianceViewHistoryLink().exists()).toBe(false);
expect(findSecurityViewHistoryLink().exists()).toBe(false);
});
+
+ it('renders training provider list with correct props', () => {
+ expect(findTrainingProviderList().props('providers')).toEqual(TRAINING_PROVIDERS);
+ });
});
describe('Manage via MR Error Alert', () => {
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
new file mode 100644
index 00000000000..1169a977d44
--- /dev/null
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -0,0 +1,60 @@
+import { GlLink, GlToggle, GlCard } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
+import { TRAINING_PROVIDERS } from '~/security_configuration/components/app.vue';
+
+const DEFAULT_PROPS = {
+ providers: TRAINING_PROVIDERS,
+};
+
+describe('TrainingProviderList component', () => {
+ let wrapper;
+
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(TrainingProviderList, {
+ propsData: {
+ ...DEFAULT_PROPS,
+ ...props,
+ },
+ });
+ };
+
+ const findCards = () => wrapper.findAllComponents(GlCard);
+ const findLinks = () => wrapper.findAllComponents(GlLink);
+ const findToggles = () => wrapper.findAllComponents(GlToggle);
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('basic structure', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('renders correct amount of cards', () => {
+ expect(findCards()).toHaveLength(DEFAULT_PROPS.providers.length);
+ });
+
+ DEFAULT_PROPS.providers.forEach(({ name, description, url, isEnabled }, index) => {
+ it(`shows the name for card ${index}`, () => {
+ expect(findCards().at(index).text()).toContain(name);
+ });
+
+ it(`shows the description for card ${index}`, () => {
+ expect(findCards().at(index).text()).toContain(description);
+ });
+
+ it(`shows the learn more link for card ${index}`, () => {
+ expect(findLinks().at(index).attributes()).toEqual({
+ target: '_blank',
+ href: url,
+ });
+ });
+
+ it(`shows the toggle with the correct value for card ${index}`, () => {
+ expect(findToggles().at(index).props('value')).toEqual(isEnabled);
+ });
+ });
+ });
+});
diff --git a/spec/helpers/invite_members_helper_spec.rb b/spec/helpers/invite_members_helper_spec.rb
index e3db2240318..d8a97b93bc9 100644
--- a/spec/helpers/invite_members_helper_spec.rb
+++ b/spec/helpers/invite_members_helper_spec.rb
@@ -16,52 +16,14 @@ RSpec.describe InviteMembersHelper do
end
describe '#common_invite_modal_dataset' do
- context 'when member_areas_of_focus is enabled', :experiment do
- context 'with control experience' do
- before do
- stub_experiments(member_areas_of_focus: :control)
- end
-
- it 'has expected attributes' do
- attributes = {
- areas_of_focus_options: [],
- no_selection_areas_of_focus: []
- }
-
- expect(helper.common_invite_modal_dataset(project)).to include(attributes)
- end
- end
-
- context 'with candidate experience' do
- before do
- stub_experiments(member_areas_of_focus: :candidate)
- end
-
- it 'has expected attributes', :aggregate_failures do
- output = helper.common_invite_modal_dataset(project)
-
- expect(output[:no_selection_areas_of_focus]).to eq ['no_selection']
- expect(Gitlab::Json.parse(output[:areas_of_focus_options]).first['value']).to eq 'Contribute to the codebase'
- end
- end
- end
-
- context 'when member_areas_of_focus is disabled' do
- before do
- stub_feature_flags(member_areas_of_focus: false)
- end
-
- it 'has expected attributes' do
- attributes = {
- id: project.id,
- name: project.name,
- default_access_level: Gitlab::Access::GUEST,
- areas_of_focus_options: [],
- no_selection_areas_of_focus: []
- }
-
- expect(helper.common_invite_modal_dataset(project)).to include(attributes)
- end
+ it 'has expected common attributes' do
+ attributes = {
+ id: project.id,
+ name: project.name,
+ default_access_level: Gitlab::Access::GUEST
+ }
+
+ expect(helper.common_invite_modal_dataset(project)).to include(attributes)
end
context 'tasks_to_be_done' do
diff --git a/spec/requests/api/invitations_spec.rb b/spec/requests/api/invitations_spec.rb
index 7b17a5ed1cb..702e6ef0a2a 100644
--- a/spec/requests/api/invitations_spec.rb
+++ b/spec/requests/api/invitations_spec.rb
@@ -152,20 +152,6 @@ RSpec.describe API::Invitations do
end
end
- context 'with areas_of_focus', :snowplow do
- it 'tracks the areas_of_focus from params' do
- post invitations_url(source, maintainer),
- params: { email: email, access_level: Member::DEVELOPER, areas_of_focus: 'Other' }
-
- expect_snowplow_event(
- category: 'Members::InviteService',
- action: 'area_of_focus',
- label: 'Other',
- property: source.members.last.id.to_s
- )
- end
- end
-
context 'with tasks_to_be_done and tasks_project_id in the params' do
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index bf5b89e72c6..02061bb8ab6 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -387,33 +387,6 @@ RSpec.describe API::Members do
end
end
- context 'with areas_of_focus considerations', :snowplow do
- let(:user_id) { stranger.id }
-
- context 'when areas_of_focus is present in params' do
- it 'tracks the areas_of_focus' do
- post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- params: { user_id: user_id, access_level: Member::DEVELOPER, areas_of_focus: 'Other' }
-
- expect_snowplow_event(
- category: 'Members::CreateService',
- action: 'area_of_focus',
- label: 'Other',
- property: source.members.last.id.to_s
- )
- end
- end
-
- context 'when areas_of_focus is not present in params' do
- it 'does not track the areas_of_focus' do
- post api("/#{source_type.pluralize}/#{source.id}/members", maintainer),
- params: { user_id: user_id, access_level: Member::DEVELOPER }
-
- expect_no_snowplow_event(category: 'Members::CreateService', action: 'area_of_focus')
- end
- end
- end
-
context 'with tasks_to_be_done and tasks_project_id in the params' do
let(:project_id) { source_type == 'project' ? source.id : create(:project, namespace: source).id }
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index babd601e0cf..34f77260334 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -79,12 +79,22 @@ RSpec.describe Ci::PlayBuildService, '#execute' do
{ key: 'second', secret_value: 'second' }]
end
+ subject { service.execute(build, job_variables) }
+
it 'assigns the variables to the build' do
- service.execute(build, job_variables)
+ subject
expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second')
end
+ context 'when variables are invalid' do
+ let(:job_variables) { [{}] }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(ActiveRecord::RecordInvalid)
+ end
+ end
+
context 'when user defined variables are restricted' do
before do
project.update!(restrict_user_defined_variables: true)
@@ -96,7 +106,7 @@ RSpec.describe Ci::PlayBuildService, '#execute' do
end
it 'assigns the variables to the build' do
- service.execute(build, job_variables)
+ subject
expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second')
end
@@ -104,8 +114,7 @@ RSpec.describe Ci::PlayBuildService, '#execute' do
context 'when user is developer' do
it 'raises an error' do
- expect { service.execute(build, job_variables) }
- .to raise_error Gitlab::Access::AccessDeniedError
+ expect { subject }.to raise_error Gitlab::Access::AccessDeniedError
end
end
end
diff --git a/spec/services/members/create_service_spec.rb b/spec/services/members/create_service_spec.rb
index 43493deb621..13f56fe7458 100644
--- a/spec/services/members/create_service_spec.rb
+++ b/spec/services/members/create_service_spec.rb
@@ -127,76 +127,6 @@ RSpec.describe Members::CreateService, :aggregate_failures, :clean_gitlab_redis_
end
end
- context 'when tracking the areas of focus', :snowplow do
- context 'when areas_of_focus is not passed' do
- it 'does not track' do
- execute_service
-
- expect_no_snowplow_event(category: described_class.name, action: 'area_of_focus')
- end
- end
-
- context 'when 1 areas_of_focus is passed' do
- let(:additional_params) { { invite_source: '_invite_source_', areas_of_focus: ['no_selection'] } }
-
- it 'tracks the areas_of_focus from params' do
- execute_service
-
- expect_snowplow_event(
- category: described_class.name,
- action: 'area_of_focus',
- label: 'no_selection',
- property: source.members.last.id.to_s
- )
- end
-
- context 'when passing many user ids' do
- let(:another_user) { create(:user) }
- let(:user_ids) { [member.id, another_user.id].join(',') }
-
- it 'tracks the areas_of_focus from params' do
- execute_service
-
- members = source.members.last(2)
-
- expect_snowplow_event(
- category: described_class.name,
- action: 'area_of_focus',
- label: 'no_selection',
- property: members.first.id.to_s
- )
- expect_snowplow_event(
- category: described_class.name,
- action: 'area_of_focus',
- label: 'no_selection',
- property: members.last.id.to_s
- )
- end
- end
- end
-
- context 'when multiple areas_of_focus are passed' do
- let(:additional_params) { { invite_source: '_invite_source_', areas_of_focus: %w[no_selection Other] } }
-
- it 'tracks the areas_of_focus from params' do
- execute_service
-
- expect_snowplow_event(
- category: described_class.name,
- action: 'area_of_focus',
- label: 'no_selection',
- property: source.members.last.id.to_s
- )
- expect_snowplow_event(
- category: described_class.name,
- action: 'area_of_focus',
- label: 'Other',
- property: source.members.last.id.to_s
- )
- end
- end
- end
-
context 'when assigning tasks to be done' do
let(:additional_params) do
{ invite_source: '_invite_source_', tasks_to_be_done: %w(ci code), tasks_project_id: source.id }
diff --git a/spec/support/helpers/features/invite_members_modal_helper.rb b/spec/support/helpers/features/invite_members_modal_helper.rb
index 3502558b2c2..11040562b49 100644
--- a/spec/support/helpers/features/invite_members_modal_helper.rb
+++ b/spec/support/helpers/features/invite_members_modal_helper.rb
@@ -5,7 +5,7 @@ module Spec
module Helpers
module Features
module InviteMembersModalHelper
- def invite_member(name, role: 'Guest', expires_at: nil, area_of_focus: false)
+ def invite_member(name, role: 'Guest', expires_at: nil)
click_on 'Invite members'
page.within '[data-testid="invite-members-modal"]' do
@@ -14,7 +14,6 @@ module Spec
wait_for_requests
click_button name
choose_options(role, expires_at)
- choose_area_of_focus if area_of_focus
click_button 'Invite'
@@ -44,13 +43,6 @@ module Spec
fill_in 'YYYY-MM-DD', with: expires_at.strftime('%Y-%m-%d') if expires_at
end
-
- def choose_area_of_focus
- page.within '[data-testid="area-of-focus-checks"]' do
- check 'Contribute to the codebase'
- check 'Collaborate on open issues and merge requests'
- end
- end
end
end
end
diff --git a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
index 00a0fb7e4c5..3a7588a5cc9 100644
--- a/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/repositories/git_http_controller_shared_examples.rb
@@ -50,7 +50,8 @@ RSpec.shared_examples Repositories::GitHttpController do
context 'with authorized user' do
before do
- request.headers.merge! auth_env(user.username, user.password, nil)
+ password = user.try(:password) || user.try(:token)
+ request.headers.merge! auth_env(user.username, password, nil)
end
it 'returns 200' do
@@ -71,9 +72,10 @@ RSpec.shared_examples Repositories::GitHttpController do
it 'adds user info to the logs' do
get :info_refs, params: params
- expect(log_data).to include('username' => user.username,
- 'user_id' => user.id,
- 'meta.user' => user.username)
+ user_log_data = { 'username' => user.username, 'user_id' => user.id }
+ user_log_data['meta.user'] = user.username if user.is_a?(User)
+
+ expect(log_data).to include(user_log_data)
end
end
end