diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-30 09:08:38 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-12-30 09:08:38 +0000 |
commit | 8ca437b79438e914b2eede563906e7889c81dc19 (patch) | |
tree | a736452eff9c2d6353498f9ad366b231103062ad | |
parent | 142658ee280a72a0cb45b241c43ddc495d852814 (diff) | |
download | gitlab-ce-8ca437b79438e914b2eede563906e7889c81dc19.tar.gz |
Add latest changes from gitlab-org/gitlab@master
10 files changed, 243 insertions, 13 deletions
diff --git a/app/assets/javascripts/notifications/components/notification_email_listbox_input.vue b/app/assets/javascripts/notifications/components/notification_email_listbox_input.vue new file mode 100644 index 00000000000..c58f4856d44 --- /dev/null +++ b/app/assets/javascripts/notifications/components/notification_email_listbox_input.vue @@ -0,0 +1,70 @@ +<script> +import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue'; + +export default { + components: { + ListboxInput, + }, + inject: { + label: { + from: 'label', + default: '', + }, + name: { + from: 'name', + }, + emails: { + from: 'emails', + default: () => [], + }, + emptyValueText: { + from: 'emptyValueText', + required: true, + }, + value: { + from: 'value', + default: '', + }, + disabled: { + from: 'disabled', + default: false, + }, + }, + data() { + return { + selected: this.value, + }; + }, + computed: { + options() { + return [ + { + value: '', + text: this.emptyValueText, + }, + ...this.emails.map((email) => ({ + text: email, + value: email, + })), + ]; + }, + }, + methods: { + async onSelect() { + await this.$nextTick(); + this.$el.closest('form').submit(); + }, + }, +}; +</script> + +<template> + <listbox-input + v-model="selected" + :label="label" + :name="name" + :items="options" + :disabled="disabled" + @select="onSelect" + /> +</template> diff --git a/app/assets/javascripts/notifications/index.js b/app/assets/javascripts/notifications/index.js index a81f2c2590b..42271b44e7c 100644 --- a/app/assets/javascripts/notifications/index.js +++ b/app/assets/javascripts/notifications/index.js @@ -2,10 +2,37 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; import { parseBoolean } from '~/lib/utils/common_utils'; import NotificationsDropdown from './components/notifications_dropdown.vue'; +import NotificationEmailListboxInput from './components/notification_email_listbox_input.vue'; Vue.use(GlToast); +const initNotificationEmailListboxInputs = () => { + const els = [...document.querySelectorAll('.js-notification-email-listbox-input')]; + + els.forEach((el, index) => { + const { label, name, emptyValueText, value } = el.dataset; + + return new Vue({ + el, + name: `NotificationEmailListboxInputRoot${index + 1}`, + provide: { + label, + name, + emails: JSON.parse(el.dataset.emails), + emptyValueText, + value, + disabled: parseBoolean(el.dataset.disabled), + }, + render(h) { + return h(NotificationEmailListboxInput); + }, + }); + }); +}; + export default () => { + initNotificationEmailListboxInputs(); + const containers = document.querySelectorAll('.js-vue-notification-dropdown'); if (!containers.length) return false; diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 93bc203d391..c031c5e5e8e 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -30,10 +30,6 @@ export default class Profile { bindEvents() { $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); - $('.js-group-notification-email').on('change', this.submitForm); - $('#user_notification_email').on('select2-selecting', (event) => { - setTimeout(this.submitForm.bind(event.currentTarget)); - }); $('#user_email_opted_in').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm); this.form.on('submit', this.onSubmitForm); diff --git a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue index a7d3bcfd59f..8340ee19084 100644 --- a/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue +++ b/app/assets/javascripts/vue_shared/components/listbox_input/listbox_input.vue @@ -19,6 +19,11 @@ export default { required: false, default: '', }, + description: { + type: String, + required: false, + default: '', + }, name: { type: String, required: true, @@ -37,6 +42,11 @@ export default { type: GlListbox.props.items.type, required: true, }, + disabled: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -44,6 +54,9 @@ export default { }; }, computed: { + wrapperComponent() { + return this.label || this.description ? 'gl-form-group' : 'div'; + }, allOptions() { const allOptions = []; @@ -102,16 +115,17 @@ export default { </script> <template> - <gl-form-group :label="label"> + <component :is="wrapperComponent" :label="label" :description="description"> <gl-listbox :selected="selected" :toggle-text="toggleText" :items="filteredItems" :searchable="isSearchable" :no-results-text="$options.i18n.noResultsText" + :disabled="disabled" @search="search" @select="$emit($options.model.event, $event)" /> <input ref="input" type="hidden" :name="name" :value="selected" /> - </gl-form-group> + </component> </template> diff --git a/app/views/profiles/notifications/_email_settings.html.haml b/app/views/profiles/notifications/_email_settings.html.haml index c4de33dcd9e..cd7a7ced1d4 100644 --- a/app/views/profiles/notifications/_email_settings.html.haml +++ b/app/views/profiles/notifications/_email_settings.html.haml @@ -1,7 +1,6 @@ - form = local_assigns.fetch(:form) .form-group - = form.label :notification_email, _('Notification Email'), class: "label-bold" - = form.select :notification_email, @user.public_verified_emails, { include_blank: _('Use primary email (%{email})') % { email: @user.email }, selected: @user.notification_email }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil) + .js-notification-email-listbox-input{ data: { label: _('Notification Email'), name: 'user[notification_email]', emails: @user.public_verified_emails.to_json, empty_value_text: _('Use primary email (%{email})') % { email: @user.email }, value: @user.notification_email, disabled: local_assigns.fetch(:email_change_disabled, nil) } } .help-block = local_assigns.fetch(:help_text, nil) .form-group diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml index 23fce8e04b6..898762ca78a 100644 --- a/app/views/profiles/notifications/_group_settings.html.haml +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -14,4 +14,4 @@ .table-section.section-30 = form_for setting, url: profile_group_notifications_path(group), method: :put, html: { class: 'update-notifications gl-display-flex' } do |f| - = f.select :notification_email, @user.public_verified_emails, { include_blank: 'Global notification email' }, class: 'select2 js-group-notification-email' + .js-notification-email-listbox-input{ data: { name: 'notification_setting[notification_email]', emails: @user.public_verified_emails.to_json, empty_value_text: _('Global notification email') , value: setting.notification_email } } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f69467e7d14..461e63df9b4 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -18829,6 +18829,9 @@ msgstr "" msgid "Global Shortcuts" msgstr "" +msgid "Global notification email" +msgstr "" + msgid "Global notification level" msgstr "" diff --git a/spec/frontend/notifications/components/notification_email_listbox_input_spec.js b/spec/frontend/notifications/components/notification_email_listbox_input_spec.js new file mode 100644 index 00000000000..c490c737cf1 --- /dev/null +++ b/spec/frontend/notifications/components/notification_email_listbox_input_spec.js @@ -0,0 +1,81 @@ +import { shallowMount } from '@vue/test-utils'; +import { nextTick } from 'vue'; +import ListboxInput from '~/vue_shared/components/listbox_input/listbox_input.vue'; +import NotificationEmailListboxInput from '~/notifications/components/notification_email_listbox_input.vue'; + +describe('NotificationEmailListboxInput', () => { + let wrapper; + + // Props + const label = 'label'; + const name = 'name'; + const emails = ['test@gitlab.com']; + const emptyValueText = 'emptyValueText'; + const value = 'value'; + const disabled = false; + + // Finders + const findListboxInput = () => wrapper.findComponent(ListboxInput); + + const createComponent = (attachTo) => { + wrapper = shallowMount(NotificationEmailListboxInput, { + provide: { + label, + name, + emails, + emptyValueText, + value, + disabled, + }, + attachTo, + }); + }; + + describe('props', () => { + beforeEach(() => { + createComponent(); + }); + + it.each` + propName | propValue + ${'label'} | ${label} + ${'name'} | ${name} + ${'selected'} | ${value} + ${'disabled'} | ${disabled} + `('passes the $propName prop to ListboxInput', ({ propName, propValue }) => { + expect(findListboxInput().props(propName)).toBe(propValue); + }); + + it('passes the options to ListboxInput', () => { + expect(findListboxInput().props('items')).toStrictEqual([ + { text: emptyValueText, value: '' }, + { text: emails[0], value: emails[0] }, + ]); + }); + }); + + describe('form', () => { + let form; + + beforeEach(() => { + form = document.createElement('form'); + const root = document.createElement('div'); + form.appendChild(root); + createComponent(root); + }); + + afterEach(() => { + form = null; + }); + + it('submits the parent form when the value changes', async () => { + jest.spyOn(form, 'submit'); + expect(form.submit).not.toHaveBeenCalled(); + + findListboxInput().vm.$emit('select'); + await nextTick(); + + expect(form.submit).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js index 2b62cbb9ab3..f7c705c495a 100644 --- a/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js +++ b/spec/frontend/vue_shared/components/listbox_input/listbox_input_spec.js @@ -7,6 +7,7 @@ describe('ListboxInput', () => { // Props const label = 'label'; + const decription = 'decription'; const name = 'name'; const defaultToggleText = 'defaultToggleText'; const items = [ @@ -32,6 +33,7 @@ describe('ListboxInput', () => { wrapper = shallowMount(ListboxInput, { propsData: { label, + decription, name, defaultToggleText, items, @@ -40,6 +42,23 @@ describe('ListboxInput', () => { }); }; + describe('wrapper', () => { + it.each` + description | labelProp | descriptionProp | rendersGlFormGroup + ${'does not render'} | ${''} | ${''} | ${false} + ${'renders'} | ${'labelProp'} | ${''} | ${true} + ${'renders'} | ${''} | ${'descriptionProp'} | ${true} + ${'renders'} | ${'labelProp'} | ${'descriptionProp'} | ${true} + `( + "$description a GlFormGroup when label is '$labelProp' and description is '$descriptionProp'", + ({ labelProp, descriptionProp, rendersGlFormGroup }) => { + createComponent({ label: labelProp, description: descriptionProp }); + + expect(findGlFormGroup().exists()).toBe(rendersGlFormGroup); + }, + ); + }); + describe('options', () => { beforeEach(() => { createComponent(); @@ -49,6 +68,10 @@ describe('ListboxInput', () => { expect(findGlFormGroup().attributes('label')).toBe(label); }); + it('passes the decription to the form group', () => { + expect(findGlFormGroup().attributes('decription')).toBe(decription); + }); + it('sets the input name', () => { expect(findInput().attributes('name')).toBe(name); }); diff --git a/spec/views/profiles/notifications/show.html.haml_spec.rb b/spec/views/profiles/notifications/show.html.haml_spec.rb index 9cdf8124fcf..1cfd8847bf8 100644 --- a/spec/views/profiles/notifications/show.html.haml_spec.rb +++ b/spec/views/profiles/notifications/show.html.haml_spec.rb @@ -5,6 +5,11 @@ require 'spec_helper' RSpec.describe 'profiles/notifications/show' do let(:groups) { GroupsFinder.new(user).execute.page(1) } let(:user) { create(:user) } + let(:option_default) { _('Use primary email (%{email})') % { email: user.email } } + let(:option_primary_email) { user.email } + let(:expected_primary_email_attr) { "[data-emails='#{[option_primary_email].to_json}']" } + let(:expected_default_attr) { "[data-empty-value-text='#{option_default}']" } + let(:expected_selector) { expected_primary_email_attr + expected_default_attr + expected_value_attr } before do assign(:group_notifications, []) @@ -16,14 +21,26 @@ RSpec.describe 'profiles/notifications/show' do end context 'when there is no database value for User#notification_email' do - let(:option_default) { _('Use primary email (%{email})') % { email: user.email } } - let(:option_primary_email) { user.email } - let(:options) { [option_default, option_primary_email] } + let(:expected_value_attr) { ":not([data-value])" } it 'displays the correct elements' do render - expect(rendered).to have_select('user_notification_email', options: options, selected: nil) + expect(rendered).to have_selector(expected_selector) + end + end + + context 'when there is a database value for User#notification_email' do + let(:expected_value_attr) { "[data-value='#{option_primary_email}']" } + + before do + user.notification_email = option_primary_email + end + + it 'displays the correct elements' do + render + + expect(rendered).to have_selector(expected_selector) end end end |