diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-03 03:11:20 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-12-03 03:11:20 +0300 |
commit | 498ba9dc41fcf2b4be30a8f3721543953efb3c3b (patch) | |
tree | ed33fbf37c0c2ae3a71042455f9b51800907a984 /app/assets/javascripts | |
parent | 515f39456fce82eb2ab811fa366167ad084a3b12 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts')
10 files changed, 174 insertions, 69 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> |