diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-02 15:11:46 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-02 15:11:46 +0300 |
commit | d8c4c08d4999959ca9b5a87a32153013791e96e0 (patch) | |
tree | 5772e13f80e3e85334f8b059c103a4065de8d682 | |
parent | aa84824d04b32ce9cd3abeac90a6bf78fb2be34c (diff) |
Add latest changes from gitlab-org/gitlab@master
68 files changed, 359 insertions, 445 deletions
diff --git a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue index 9a7296b6b1f..3b71e39d69b 100644 --- a/app/assets/javascripts/access_tokens/components/access_token_table_app.vue +++ b/app/assets/javascripts/access_tokens/components/access_token_table_app.vue @@ -158,7 +158,7 @@ export default { :aria-label="$options.i18n.revokeButton" :data-confirm="modalMessage(name)" data-confirm-btn-variant="danger" - data-qa-selector="revoke_button" + data-testid="revoke-button" data-method="put" :href="revokePath" icon="remove" diff --git a/app/assets/javascripts/access_tokens/components/expires_at_field.vue b/app/assets/javascripts/access_tokens/components/expires_at_field.vue index 38501d63d3a..65206670a3c 100644 --- a/app/assets/javascripts/access_tokens/components/expires_at_field.vue +++ b/app/assets/javascripts/access_tokens/components/expires_at_field.vue @@ -68,7 +68,7 @@ export default { :input-name="inputAttrs.name" :input-id="inputAttrs.id" :placeholder="inputAttrs.placeholder" - data-qa-selector="expiry_date_field" + data-testid="expiry-date-field" /> <template #description> <template v-if="description"> diff --git a/app/assets/javascripts/access_tokens/components/new_access_token_app.vue b/app/assets/javascripts/access_tokens/components/new_access_token_app.vue index 4b51b4333aa..f476503c091 100644 --- a/app/assets/javascripts/access_tokens/components/new_access_token_app.vue +++ b/app/assets/javascripts/access_tokens/components/new_access_token_app.vue @@ -45,7 +45,7 @@ export default { formInputGroupProps() { return { id: this.$options.tokenInputId, - 'data-qa-selector': 'created_access_token_field', + 'data-testid': 'created-access-token-field', name: this.$options.tokenInputId, }; }, @@ -110,7 +110,7 @@ export default { @[$options.EVENT_ERROR]="onError" @[$options.EVENT_SUCCESS]="onSuccess" > - <div ref="container" data-testid="access-token-section" data-qa-selector="access_token_section"> + <div ref="container" data-testid="access-token-section"> <gl-alert v-if="newToken" variant="success" diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue index d3b914ea8aa..240bf005532 100644 --- a/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue +++ b/app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue @@ -115,14 +115,10 @@ export default { </gl-sprintf> </p> - <gl-card - class="codes-to-print gl-my-5" - data-testid="recovery-codes" - data-qa-selector="codes_content" - > + <gl-card class="codes-to-print gl-my-5" data-testid="recovery-codes"> <ul class="gl-m-0 gl-pl-5"> <li v-for="(code, index) in codes" :key="index"> - <span class="gl-font-monospace" data-qa-selector="code_content">{{ code }}</span> + <span class="gl-font-monospace" data-testid="code-content">{{ code }}</span> </li> </ul> </gl-card> @@ -131,7 +127,7 @@ export default { <clipboard-button :title="$options.i18n.copyButton" :text="codesAsString" - data-qa-selector="copy_button" + data-testid="copy-button" @click="handleButtonClick($options.copyButtonAction)" > {{ $options.i18n.copyButton }} @@ -163,7 +159,7 @@ export default { :disabled="proceedButtonDisabled" :title="$options.i18n.proceedButton" variant="confirm" - data-qa-selector="proceed_button" + data-testid="proceed-button" data-track-action="click_button" :data-track-label="`${$options.trackingLabelPrefix}proceed_button`" >{{ $options.i18n.proceedButton }}</gl-button diff --git a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue index 424a9d3fabd..7db315fda1a 100644 --- a/app/assets/javascripts/invite_members/components/invite_group_trigger.vue +++ b/app/assets/javascripts/invite_members/components/invite_group_trigger.vue @@ -28,12 +28,7 @@ export default { </script> <template> - <gl-button - :class="classes" - data-qa-selector="invite_a_group_button" - data-test-id="invite-group-button" - @click="openModal" - > + <gl-button :class="classes" data-testid="invite-a-group-button" @click="openModal"> {{ displayText }} </gl-button> </template> diff --git a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue index 6efb7a6cdf1..7f76b7ca1ac 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_trigger.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_trigger.vue @@ -4,7 +4,6 @@ import { s__ } from '~/locale'; import eventHub from '../event_hub'; import { TRIGGER_ELEMENT_BUTTON, - TRIGGER_DEFAULT_QA_SELECTOR, TRIGGER_ELEMENT_WITH_EMOJI, TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI, TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN, @@ -42,18 +41,12 @@ export default { required: false, default: 'button', }, - qaSelector: { - type: String, - required: false, - default: TRIGGER_DEFAULT_QA_SELECTOR, - }, }, computed: { componentAttributes() { return { class: this.classes, - 'data-qa-selector': this.qaSelector, - 'data-test-id': 'invite-members-button', + 'data-testid': 'invite-members-button', }; }, item() { diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index 18d22395104..20b7096785d 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -173,7 +173,6 @@ export default { variant: 'confirm', disabled: this.submitDisabled, loading: this.isLoading, - 'data-qa-selector': 'invite_button', }, }; }, @@ -311,7 +310,7 @@ export default { <gl-form-select :id="dropdownId" v-model="selectedAccessLevel" - data-qa-selector="access_level_dropdown" + data-testid="access-level-dropdown" :options="accessLevelsOptions" /> </gl-form-group> diff --git a/app/assets/javascripts/invite_members/components/members_token_select.vue b/app/assets/javascripts/invite_members/components/members_token_select.vue index 0be04b7af35..015cadc9993 100644 --- a/app/assets/javascripts/invite_members/components/members_token_select.vue +++ b/app/assets/javascripts/invite_members/components/members_token_select.vue @@ -102,7 +102,6 @@ export default { textInputAttrs() { return { 'data-testid': 'members-token-select-input', - 'data-qa-selector': 'members_token_select_input', id: this.inputId, }; }, diff --git a/app/assets/javascripts/invite_members/constants.js b/app/assets/javascripts/invite_members/constants.js index 93386e5504b..3b2840ecf11 100644 --- a/app/assets/javascripts/invite_members/constants.js +++ b/app/assets/javascripts/invite_members/constants.js @@ -20,7 +20,6 @@ export const TRIGGER_ELEMENT_WITH_EMOJI = 'text-emoji'; export const TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI = 'dropdown-text-emoji'; export const TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN = 'dropdown-text'; export const INVITE_MEMBER_MODAL_TRACKING_CATEGORY = 'invite_members_modal'; -export const TRIGGER_DEFAULT_QA_SELECTOR = 'invite_members_button'; export const IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_CATEGORY = 'invite_project_members_modal'; export const IMPORT_PROJECT_MEMBERS_MODAL_TRACKING_LABEL = 'project-members-page'; export const MEMBERS_MODAL_DEFAULT_TITLE = s__('InviteMembersModal|Invite members'); diff --git a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js index 8fe822e4639..41952a33c05 100644 --- a/app/assets/javascripts/pages/profiles/two_factor_auths/index.js +++ b/app/assets/javascripts/pages/profiles/two_factor_auths/index.js @@ -8,7 +8,7 @@ const skippable = twoFactorNode ? parseBoolean(twoFactorNode.dataset.twoFactorSk if (skippable) { const button = `<div class="gl-alert-actions"> - <a class="btn gl-button btn-md btn-confirm gl-alert-action" data-qa-selector="configure_it_later_button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a> + <a class="btn gl-button btn-md btn-confirm gl-alert-action" data-testid="configure-it-later-button" data-method="patch" href="${twoFactorNode.dataset.two_factor_skip_url}">Configure it later</a> </div>`; const flashAlert = document.querySelector('.flash-alert'); if (flashAlert) { diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue index 915f6578ac3..e9a67a401b8 100644 --- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue +++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue @@ -42,7 +42,7 @@ export default { text: __('Delete account'), attributes: { variant: 'danger', - 'data-qa-selector': 'confirm_delete_account_button', + 'data-testid': 'confirm-delete-account-button', category: 'primary', disabled: !this.canSubmit, }, @@ -128,7 +128,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), name="password" class="form-control" type="password" - data-qa-selector="password_confirmation_field" + data-testid="password-confirmation-field" aria-labelledby="input-label" /> <input diff --git a/app/assets/javascripts/super_sidebar/components/user_menu.vue b/app/assets/javascripts/super_sidebar/components/user_menu.vue index 891e883b6c0..c3655572e2f 100644 --- a/app/assets/javascripts/super_sidebar/components/user_menu.vue +++ b/app/assets/javascripts/super_sidebar/components/user_menu.vue @@ -88,7 +88,7 @@ export default { text: this.$options.i18n.editProfile, href: this.data.settings.profile_path, extraAttrs: { - 'data-testid': 'edit_profile_link', + 'data-testid': 'edit-profile-link', ...USER_MENU_TRACKING_DEFAULTS, 'data-track-label': 'user_edit_profile', }, @@ -235,7 +235,7 @@ export default { :entity-name="data.name" :src="data.avatar_url" aria-hidden="true" - data-testid="user_avatar_content" + data-testid="user-avatar-content" /> <span v-if="showNotificationDot" diff --git a/app/assets/javascripts/terms/components/app.vue b/app/assets/javascripts/terms/components/app.vue index 29099bcc366..75ee0e16d4e 100644 --- a/app/assets/javascripts/terms/components/app.vue +++ b/app/assets/javascripts/terms/components/app.vue @@ -66,7 +66,7 @@ export default { <template> <div> - <div class="gl-relative gl-pb-0 gl-px-0" data-qa-selector="terms_content"> + <div class="gl-relative gl-pb-0 gl-px-0" data-testid="terms-content"> <div class="terms-fade gl-absolute gl-left-5 gl-right-5 gl-bottom-0 gl-h-11 gl-pointer-events-none" ></div> @@ -97,7 +97,7 @@ export default { type="submit" variant="confirm" :disabled="acceptDisabled" - data-qa-selector="accept_terms_button" + data-testid="accept-terms-button" >{{ $options.i18n.accept }}</gl-button > <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue index 8c46c6152ce..e84b3f53b53 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue @@ -30,7 +30,7 @@ import AlertSidebar from './alert_sidebar.vue'; import AlertSummaryRow from './alert_summary_row.vue'; import SystemNote from './system_notes/system_note.vue'; -const containerEl = document.querySelector('.page-with-contextual-sidebar'); +const containerEl = document.querySelector('.layout-page'); export default { i18n: { diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue index a1ef1f30ebb..5019ab901fd 100644 --- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue +++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue @@ -72,7 +72,7 @@ export default { attributes: { variant: 'danger', disabled: !this.isValid, - 'data-qa-selector': 'confirm_danger_modal_button', + 'data-testid': 'confirm-danger-modal-button', }, }; }, @@ -133,8 +133,7 @@ export default { id="confirm_name_input" v-model="confirmationPhrase" class="form-control" - data-qa-selector="confirm_danger_field" - data-testid="confirm-danger-input" + data-testid="confirm-danger-field" type="text" /> </gl-form-group> diff --git a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue index d97f1ae6135..344edc1082e 100644 --- a/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue +++ b/app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue @@ -176,7 +176,6 @@ export default { :aria-label="toggleVisibilityLabel" :icon="toggleVisibilityIcon" data-testid="toggle-visibility-button" - data-qa-selector="toggle_visibility_button" @click.stop="handleToggleVisibilityButtonClick" /> <clipboard-button diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index fc157df3891..2d6c9d4a068 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -47,13 +47,13 @@ module AuthHelper provider_has_builtin_icon?(name) || provider_has_custom_icon?(name) end - def qa_selector_for_provider(provider) + def test_id_for_provider(provider) { - saml: 'saml_login_button', - openid_connect: 'oidc_login_button', - github: 'github_login_button', - gitlab: 'gitlab_oauth_login_button', - facebook: 'facebook_login_button' + saml: 'saml-login-button', + openid_connect: 'oidc-login-button', + github: 'github-login-button', + gitlab: 'gitlab-oauth-login-button', + facebook: 'facebook-login-button' }[provider.to_sym] end diff --git a/app/services/admin/plan_limits/update_service.rb b/app/services/admin/plan_limits/update_service.rb index 24ce3c4095f..7412f9852d1 100644 --- a/app/services/admin/plan_limits/update_service.rb +++ b/app/services/admin/plan_limits/update_service.rb @@ -51,35 +51,63 @@ module Admin def validate_notification_limit return unless parsed_params.include?(:notification_limit) - return if notification_limit >= storage_size_limit && notification_limit <= enforcement_limit + return if unlimited_value?(:notification_limit) - plan_limits.errors.add(:notification_limit, "must be greater than or equal to " \ - "storage_size_limit (Dashboard limit): #{storage_size_limit} " \ - "and less than or equal to enforcement_limit: #{enforcement_limit}") + if storage_size_limit > 0 && notification_limit < storage_size_limit + plan_limits.errors.add( + :notification_limit, "must be greater than or equal to the dashboard limit (#{storage_size_limit})" + ) + end + + return unless enforcement_limit > 0 && notification_limit > enforcement_limit + + plan_limits.errors.add( + :notification_limit, "must be less than or equal to the enforcement limit (#{enforcement_limit})" + ) end def validate_enforcement_limit return unless parsed_params.include?(:enforcement_limit) - return if enforcement_limit >= storage_size_limit && enforcement_limit >= notification_limit + return if unlimited_value?(:enforcement_limit) + + if storage_size_limit > 0 && enforcement_limit < storage_size_limit + plan_limits.errors.add( + :enforcement_limit, "must be greater than or equal to the dashboard limit (#{storage_size_limit})" + ) + end + + return unless notification_limit > 0 && enforcement_limit < notification_limit - plan_limits.errors.add(:enforcement_limit, "must be greater than or equal to " \ - "storage_size_limit (Dashboard limit): #{storage_size_limit} and " \ - "greater than or equal to notification_limit: #{notification_limit}") + plan_limits.errors.add( + :enforcement_limit, "must be greater than or equal to the notification limit (#{notification_limit})" + ) end def validate_storage_size_limit return unless parsed_params.include?(:storage_size_limit) - return if storage_size_limit <= enforcement_limit && storage_size_limit <= notification_limit + return if unlimited_value?(:storage_size_limit) - plan_limits.errors.add(:storage_size_limit, "(Dashboard limit) must be less than or equal to " \ - "enforcement_limit: #{enforcement_limit} " \ - "and notification_limit: #{notification_limit}") + if enforcement_limit > 0 && storage_size_limit > enforcement_limit + plan_limits.errors.add( + :dashboard_limit, "must be less than or equal to the enforcement limit (#{enforcement_limit})" + ) + end + + return unless notification_limit > 0 && storage_size_limit > notification_limit + + plan_limits.errors.add( + :dashboard_limit, "must be less than or equal to the notification limit (#{notification_limit})" + ) end # Overridden in EE def parsed_params params end + + def unlimited_value?(limit) + parsed_params[limit] == 0 + end end end end diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index 35ee9a7679a..7dd4d119a62 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -7,13 +7,13 @@ = f.hidden_field :reset_password_token .form-group.gl-px-5 = f.label _('New password'), for: "user_password" - = f.password_field :password, autocomplete: 'new-password', class: "form-control gl-form-input top js-password-complexity-validation", required: true, title: _('This field is required.'), data: { qa_selector: 'password_field'} + = f.password_field :password, autocomplete: 'new-password', class: "form-control gl-form-input top js-password-complexity-validation", required: true, title: _('This field is required.'), data: { testid: 'password-field'} = render_if_exists 'shared/password_requirements_list' .form-group.gl-px-5 = f.label _('Confirm new password'), for: "user_password_confirmation" - = f.password_field :password_confirmation, autocomplete: 'new-password', class: "form-control gl-form-input bottom", title: _('This field is required.'), data: { qa_selector: 'password_confirmation_field' }, required: true + = f.password_field :password_confirmation, autocomplete: 'new-password', class: "form-control gl-form-input bottom", title: _('This field is required.'), data: { testid: 'password-confirmation-field' }, required: true .clearfix.gl-px-5.gl-pb-5 - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { qa_selector: 'change_password_button' } }) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'change-password-button' } }) do = _('Change your password') .clearfix.prepend-top-20 diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 88dd4fd1721..d9b7c986a9f 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -1,11 +1,10 @@ = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'gl-p-5 gl-show-field-errors js-arkose-labs-form', aria: { live: 'assertive' }, data: { testid: 'sign-in-form' }}) do |f| .form-group = f.label :login, _('Username or primary email') - = f.text_field :login, value: @invite_email, class: 'form-control gl-form-input js-username-field', autocomplete: 'username', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field', testid: 'username-field' } + = f.text_field :login, value: @invite_email, class: 'form-control gl-form-input js-username-field', autocomplete: 'username', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { testid: 'username-field' } .form-group = f.label :password, _('Password') = f.password_field :password, class: 'form-control gl-form-input js-password', data: { id: "#{resource_name}_password", - qa_selector: 'password_field', testid: 'password-field', name: "#{resource_name}[password]" } .form-text.gl-text-right @@ -22,5 +21,5 @@ .form-group = f.gitlab_ui_checkbox_component :remember_me, _('Remember me'), checkbox_options: { autocomplete: 'off' } - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { class: 'js-sign-in-button', data: { qa_selector: 'sign_in_button', testid: 'sign-in-button' } }) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { class: 'js-sign-in-button', data: { testid: 'sign-in-button' } }) do = _('Sign in') diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 471cc053e6e..db7b7a4f729 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -6,13 +6,13 @@ = gitlab_ui_form_for(provider, url: omniauth_callback_path(:user, provider), html: { class: 'gl-p-5 gl-show-field-errors', aria: { live: 'assertive' }, data: { testid: 'new_ldap_user' }}) do |f| .form-group = f.label :username, _('Username') - = f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input', title: _('This field is required.'), autofocus: 'autofocus', data: { qa_selector: 'username_field' }, required: true + = f.text_field :username, name: :username, autocomplete: :username, class: 'form-control gl-form-input', title: _('This field is required.'), autofocus: 'autofocus', data: { testid: 'username-field' }, required: true .form-group = f.label :password, _('Password') - %input.form-control.gl-form-input.js-password{ data: { id: "#{provider}_password", name: 'password', qa_selector: 'password_field' } } + %input.form-control.gl-form-input.js-password{ data: { id: "#{provider}_password", name: 'password', testid: 'password-field' } } - if render_remember_me = f.gitlab_ui_checkbox_component :remember_me, _('Remember me'), checkbox_options: { name: :remember_me, autocomplete: 'off' } - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { qa_selector: 'sign_in_button' } }) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'sign-in-button' } }) do = submit_message diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index acfb16b64cd..1caf0bb5893 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -29,6 +29,6 @@ - if allow_signup? %p{ class: "gl-mt-3 #{'gl-text-center' if Feature.enabled?(:restyle_login_page, @project)}" } = _("Don't have an account yet?") - = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { qa_selector: 'register_link' } + = link_to _("Register now"), new_registration_path(:user, invite_email: @invite_email), data: { testid: 'register-link' } - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled? = render 'devise/shared/omniauth_box' diff --git a/app/views/devise/sessions/two_factor.html.haml b/app/views/devise/sessions/two_factor.html.haml index e3457040e6c..96f6f5cb095 100644 --- a/app/views/devise/sessions/two_factor.html.haml +++ b/app/views/devise/sessions/two_factor.html.haml @@ -5,7 +5,7 @@ = gitlab_ui_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post, html: { class: "edit_user gl-show-field-errors js-2fa-form #{'hidden' if @user.two_factor_webauthn_enabled?}" }) do |f| .form-group = f.label :otp_attempt, _('Enter verification code') - = f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { qa_selector: 'two_fa_code_field' } + = f.text_field :otp_attempt, class: 'form-control gl-form-input', required: true, autofocus: true, autocomplete: 'off', inputmode: 'numeric', title: _('This field is required.'), data: { testid: 'two-fa-code-field' } %p.form-text.text-muted.hint = _("Enter the code from your two-factor authenticator app. If you've lost your device, you can enter one of your recovery codes.") @@ -13,7 +13,7 @@ - resource_params = params[resource_name].presence || params = f.hidden_field :remember_me, value: resource_params.fetch(:remember_me, 0) - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { qa_selector: 'verify_code_button' } }) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, block: true, button_options: { data: { testid: 'verify-code-button' } }) do = _("Verify code") - if @user.two_factor_webauthn_enabled? diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 73b9a3d5c5a..45062745b77 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -11,7 +11,7 @@ = _('Sign in with') - enabled_button_based_providers.each do |provider| - has_icon = provider_has_icon?(provider) - = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", data: { qa_selector: "#{qa_selector_for_provider(provider)}" }, class: "btn gl-button btn-default gl-mb-2 js-oauth-login gl-w-full", form: { class: restyle_login_page_enabled ? 'gl-mb-3' : 'gl-w-full gl-mb-3' } do + = button_to omniauth_authorize_path(:user, provider), id: "oauth-login-#{provider}", data: { testid: "#{test_id_for_provider(provider)}" }, class: "btn gl-button btn-default gl-mb-2 js-oauth-login gl-w-full", form: { class: restyle_login_page_enabled ? 'gl-mb-3' : 'gl-w-full gl-mb-3' } do - if has_icon = provider_image_tag(provider) %span.gl-button-text diff --git a/app/views/devise/shared/_signup_omniauth_provider_list.haml b/app/views/devise/shared/_signup_omniauth_provider_list.haml index b9efcaa11b4..da778120e01 100644 --- a/app/views/devise/shared/_signup_omniauth_provider_list.haml +++ b/app/views/devise/shared/_signup_omniauth_provider_list.haml @@ -4,7 +4,7 @@ = _("Register with:") .gl-text-center.gl-ml-auto.gl-mr-auto - providers.each do |provider| - = button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do + = button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{test_id_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do - if provider_has_icon?(provider) = provider_image_tag(provider) %span.gl-button-text @@ -15,7 +15,7 @@ .gl-display-flex.gl-justify-content-between.gl-flex-wrap - providers.each do |provider| = button_to omniauth_authorize_path(:user, provider, register_omniauth_params(local_assigns)), - class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{qa_selector_for_provider(provider)}", + class: "btn gl-button btn-default gl-w-full gl-mb-4 js-oauth-login #{test_id_for_provider(provider)}", data: { provider: provider, track_action: "#{provider}_sso", track_label: tracking_label }, id: "oauth-login-#{provider}" do - if provider_has_icon?(provider) diff --git a/app/views/devise/shared/_tab_single.html.haml b/app/views/devise/shared/_tab_single.html.haml index b7ba8870df5..9348a5e3451 100644 --- a/app/views/devise/shared/_tab_single.html.haml +++ b/app/views/devise/shared/_tab_single.html.haml @@ -1,2 +1,2 @@ = gl_tabs_nav({ class: 'new-session-tabs gl-border-0' }) do - = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { qa_selector: 'sign_in_tab', testid: 'sign-in-tab' } } + = gl_tab_link_to tab_title, '#', { item_active: true, class: 'gl-cursor-default!', tab_class: 'gl-bg-transparent!', tabindex: '-1', data: { testid: 'sign-in-tab' } } diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml index 76c4cf41a2d..e6bc38ba6dd 100644 --- a/app/views/devise/shared/_tabs_ldap.html.haml +++ b/app/views/devise/shared/_tabs_ldap.html.haml @@ -8,13 +8,13 @@ = render_if_exists "devise/shared/kerberos_tab" - ldap_servers.each_with_index do |server, i| %li.nav-item - = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab', testid: 'ldap-tab' }, role: 'tab' + = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i == 0 && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', testid: 'ldap-tab' }, role: 'tab' = render_if_exists 'devise/shared/tab_smartcard' - if show_password_form %li.nav-item - = link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' }, role: 'tab' + = link_to _('Standard'), '#login-pane', class: 'nav-link', data: { toggle: 'tab', testid: 'standard-tab' }, role: 'tab' - if render_signup_link && allow_signup? %li.nav-item - = link_to _('Register'), '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' + = link_to _('Register'), '#register-pane', class: 'nav-link', data: { toggle: 'tab', testid: 'register-tab' }, role: 'tab' diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml index fd5088e04b0..116732cd98d 100644 --- a/app/views/doorkeeper/authorizations/new.html.haml +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -51,5 +51,5 @@ = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method = render Pajamas::ButtonComponent.new(type: :submit, variant: :danger, - button_options: { id: 'commit-changes', class: 'gl-ml-3', qa_selector: 'authorization_button'}) do + button_options: { id: 'commit-changes', class: 'gl-ml-3', testid: 'authorization_button'}) do = _("Authorize") diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 4e9ae7c7fd8..7794e3c9853 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -3,7 +3,7 @@ !!! 5 %html.html-devise-layout{ lang: I18n.locale } = render "layouts/head", { startup_filename: 'signin' } - %body.gl-h-full.login-page.navless{ class: "#{system_message_class} #{user_application_theme} #{client_class_list}", data: { page: body_data_page, qa_selector: 'login_page' } } + %body.gl-h-full.login-page.navless{ class: "#{system_message_class} #{user_application_theme} #{client_class_list}", data: { page: body_data_page, testid: 'login-page' } } = header_message = render "layouts/init_client_detection_flags" - if Feature.enabled?(:restyle_login_page, @project) diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 993094c6889..fb875c67b6d 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -110,7 +110,7 @@ - if header_link?(:user_dropdown) %li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { testid: 'user-dropdown' }, class: ('mr-0' if has_impersonation_link) } = link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown", track_label: "profile_dropdown", track_action: "click_dropdown", track_property: "navigation_top" } do - = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar', avatar_options: { data: { testid: 'user_avatar_content' } }) + = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar', avatar_options: { data: { testid: 'user-avatar-content' } }) = render_if_exists 'layouts/header/user_notification_dot', project: project, namespace: group = sprite_icon('chevron-down', css_class: 'caret-down') .dropdown-menu.dropdown-menu-right diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml index 32f00a4c0c6..621c0579226 100644 --- a/app/views/layouts/terms.html.haml +++ b/app/views/layouts/terms.html.haml @@ -25,6 +25,6 @@ .gl-text-right.gl-line-height-normal .gl-font-weight-bold= current_user.name .gl-text-gray-700 @#{current_user.username} - = render Pajamas::AvatarComponent.new(current_user, size: 32, avatar_options: { data: { qa_selector: 'user_avatar_content' } }) + = render Pajamas::AvatarComponent.new(current_user, size: 32, avatar_options: { data: { testid: 'user-avatar-content' } }) - c.with_body do = yield diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 799dfaae8c5..75dba925328 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -30,7 +30,7 @@ = render Pajamas::ButtonComponent.new(variant: :confirm, href: profile_two_factor_auth_path) do = _('Manage two-factor authentication') - else - = render Pajamas::ButtonComponent.new(variant: :confirm, href: profile_two_factor_auth_path, button_options: { data: { qa_selector: 'enable_2fa_button' }}) do + = render Pajamas::ButtonComponent.new(variant: :confirm, href: profile_two_factor_auth_path, button_options: { data: { testid: 'enable-2fa-button' }}) do = _('Enable two-factor authentication') - if display_providers_on_profile? @@ -76,7 +76,7 @@ = render 'users/deletion_guidance', user: current_user -# Delete button here - = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { id: 'delete-account-button', disabled: true, data: { qa_selector: 'delete_account_button' }}) do + = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { id: 'delete-account-button', disabled: true, data: { testid: 'delete-account-button' }}) do = s_('Profiles|Delete account') #delete-account-modal{ data: { action_url: user_registration_path, diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index ff0b31da022..25badbcdb16 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -29,7 +29,7 @@ = _("To add the entry manually, provide the following details to the application on your phone.") %p.gl-mt-0.gl-mb-0 = _('Account: %{account}') % { account: @account_string } - %p.gl-mt-0.gl-mb-0{ data: { qa_selector: 'otp_secret_content' } } + %p.gl-mt-0.gl-mb-0{ data: { testid: 'otp-secret-content' } } = _('Key:') %code.two-factor-secret= current_user.otp_secret.scan(/.{4}/).join(' ') %p.gl-mb-0.two-factor-new-manual-content @@ -46,14 +46,14 @@ - if current_password_required? .form-group = label_tag :current_password, _('Current password'), class: 'label-bold' - = password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { qa_selector: 'current_password_field' } + = password_field_tag :current_password, nil, autocomplete: 'current-password', required: true, class: 'form-control gl-form-input', data: { testid: 'current-password-field' } %p.form-text.text-muted = _('Your current password is required to register a two-factor authenticator app.') .form-group = label_tag :pin_code, _('Enter verification code'), class: "label-bold" - = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { qa_selector: 'pin_code_field' } + = text_field_tag :pin_code, nil, autocomplete: 'off', inputmode: 'numeric', class: "form-control gl-form-input", required: true, data: { testid: 'pin-code-field' } .gl-mt-3 - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { qa_selector: 'register_2fa_app_button' } }) do + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm, button_options: { data: { testid: 'register-2fa-app-button' } }) do = _('Register with two-factor app') %hr diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml index 3bf85da83b1..79c4bfca630 100644 --- a/app/views/shared/access_tokens/_form.html.haml +++ b/app/views/shared/access_tokens/_form.html.haml @@ -15,7 +15,7 @@ .form-group = f.label :name, s_('AccessTokens|Token name'), class: 'label-bold' - = f.text_field :name, class: 'form-control gl-form-input gl-form-input-xl', required: true, data: { qa_selector: 'access_token_name_field' }, :'aria-describedby' => 'access_token_help_text' + = f.text_field :name, class: 'form-control gl-form-input gl-form-input-xl', required: true, data: { testid: 'access-token-name-field' }, :'aria-describedby' => 'access_token_help_text' %span.form-text.text-muted#access_token_help_text - if resource - resource_type = resource.is_a?(Group) ? "group" : "project" @@ -42,6 +42,6 @@ = render 'shared/tokens/scopes_form', prefix: prefix, description_prefix: description_prefix, token: token, scopes: scopes, f: f .gl-mt-3 - = f.submit s_('AccessTokens|Create %{type}') % { type: type }, data: { qa_selector: 'create_token_button' }, pajamas_button: true + = f.submit s_('AccessTokens|Create %{type}') % { type: type }, data: { testid: 'create-token-button' }, pajamas_button: true = render Pajamas::ButtonComponent.new(button_options: { type: 'reset', class: 'gl-ml-2 js-toggle-button' }) do = _('Cancel') diff --git a/app/views/shared/deploy_tokens/_form.html.haml b/app/views/shared/deploy_tokens/_form.html.haml index bb7e0d774cc..44d261b84f1 100644 --- a/app/views/shared/deploy_tokens/_form.html.haml +++ b/app/views/shared/deploy_tokens/_form.html.haml @@ -6,12 +6,12 @@ .form-group = f.label :name, class: 'label-bold' - = f.text_field :name, class: 'form-control gl-form-input', data: { qa_selector: 'deploy_token_name_field' }, required: true + = f.text_field :name, class: 'form-control gl-form-input', data: { testid: 'deploy-token-name-field' }, required: true .text-secondary= s_('DeployTokens|Enter a unique name for your deploy token.') .form-group = f.label :expires_at, _('Expiration date (optional)'), class: 'label-bold' - = f.gitlab_ui_datepicker :expires_at, data: { qa_selector: 'deploy_token_expires_at_field' }, value: f.object.expires_at + = f.gitlab_ui_datepicker :expires_at, data: { testid: 'deploy-token-expires-at-field' }, value: f.object.expires_at .text-secondary= s_('DeployTokens|Enter an expiration date for your token. Defaults to never expire.') .form-group @@ -22,15 +22,15 @@ .form-group = f.label :scopes, _('Scopes (select at least one)'), class: 'label-bold' - = f.gitlab_ui_checkbox_component :read_repository, 'read_repository', help_text: s_('DeployTokens|Allows read-only access to the repository.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_repository_checkbox' } } + = f.gitlab_ui_checkbox_component :read_repository, 'read_repository', help_text: s_('DeployTokens|Allows read-only access to the repository.'), checkbox_options: { data: { testid: 'deploy-token-read-repository-checkbox' } } - if container_registry_enabled?(group_or_project) - = f.gitlab_ui_checkbox_component :read_registry, 'read_registry', help_text: s_('DeployTokens|Allows read-only access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_registry_checkbox' } } - = f.gitlab_ui_checkbox_component :write_registry, 'write_registry', help_text: s_('DeployTokens|Allows write access to registry images.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_registry_checkbox' } } + = f.gitlab_ui_checkbox_component :read_registry, 'read_registry', help_text: s_('DeployTokens|Allows read-only access to registry images.'), checkbox_options: { data: { testid: 'deploy-token-read-registry-checkbox' } } + = f.gitlab_ui_checkbox_component :write_registry, 'write_registry', help_text: s_('DeployTokens|Allows write access to registry images.'), checkbox_options: { data: { testid: 'deploy-token-write-registry-checkbox' } } - if packages_registry_enabled?(group_or_project) - = f.gitlab_ui_checkbox_component :read_package_registry, 'read_package_registry', help_text: s_('DeployTokens|Allows read-only access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_read_package_registry_checkbox' } } - = f.gitlab_ui_checkbox_component :write_package_registry, 'write_package_registry', help_text: s_('DeployTokens|Allows read and write access to the package registry.'), checkbox_options: { data: { qa_selector: 'deploy_token_write_package_registry_checkbox' } } + = f.gitlab_ui_checkbox_component :read_package_registry, 'read_package_registry', help_text: s_('DeployTokens|Allows read-only access to the package registry.'), checkbox_options: { data: { testid: 'deploy-token-read-package-registry-checkbox' } } + = f.gitlab_ui_checkbox_component :write_package_registry, 'write_package_registry', help_text: s_('DeployTokens|Allows read and write access to the package registry.'), checkbox_options: { data: { testid: 'deploy-token-write-package-registry-checkbox' } } .gl-mt-3 - = f.submit s_('DeployTokens|Create deploy token'), data: { qa_selector: 'create_deploy_token_button' }, pajamas_button: true + = f.submit s_('DeployTokens|Create deploy token'), data: { testid: 'create-deploy-token-button' }, pajamas_button: true diff --git a/app/views/shared/deploy_tokens/_new_deploy_token.html.haml b/app/views/shared/deploy_tokens/_new_deploy_token.html.haml index 30917ee6fff..2bc2e6c5b81 100644 --- a/app/views/shared/deploy_tokens/_new_deploy_token.html.haml +++ b/app/views/shared/deploy_tokens/_new_deploy_token.html.haml @@ -1,11 +1,11 @@ -.created-deploy-token-container.info-well{ data: { qa_selector: 'created_deploy_token_container' } } +.created-deploy-token-container.info-well{ data: { testid: 'created-deploy-token-container' } } .well-segment %h5.gl-mt-0 = s_('DeployTokens|Your new Deploy Token username') .form-group .input-group - = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus', data: { qa_selector: 'deploy_token_user_field' } + = text_field_tag 'deploy-token-user', deploy_token.username, readonly: true, class: 'deploy-token-field form-control js-select-on-focus', data: { testid: 'deploy-token-user-field' } .input-group-append = deprecated_clipboard_button(text: deploy_token.username, title: s_('DeployTokens|Copy username'), placement: 'left') %span.deploy-token-help-block.gl-mt-2.text-success @@ -15,7 +15,7 @@ .form-group .input-group - = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus', data: { qa_selector: 'deploy_token_field' } + = text_field_tag 'deploy-token', deploy_token.token, readonly: true, class: 'deploy-token-field form-control js-select-on-focus', data: { testid: 'deploy-token-field' } .input-group-append = deprecated_clipboard_button(text: deploy_token.token, title: s_('DeployTokens|Copy deploy token'), placement: 'left') %span.deploy-token-help-block.gl-mt-2.text-danger diff --git a/doc/api/users.md b/doc/api/users.md index a1159441056..cb9951a1c45 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -2336,6 +2336,7 @@ Prerequisites: - You must be an administrator or have the Owner role of the target namespace or project. - For `instance_type`, you must be an administrator of the GitLab instance. +- For `group_type` or `project_type` with an Owner role, an administrator must not have enabled [restrict runner registration](../administration/settings/continuous_integration.md#restrict-runner-registration-by-all-users-in-an-instance). - An access token with the `create_runner` scope. Be sure to copy or save the `token` in the response, the value cannot be retrieved again. diff --git a/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md b/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md deleted file mode 100644 index 70e696def96..00000000000 --- a/doc/architecture/blueprints/gcp_integration/decisions/001_no_credentials.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -description: 'GCP Integration ADR 001: Do not store GCP credentials in GitLab' ---- - -# GCP Integration ADR 001: Do not store GCP credentials in GitLab - -## Context - -Users need to be able to perform certain actions within GCP based on their -GitLab project membership and permissions. For example, users of the -integration need a mechanism to push packages to the Google Artifact Registry -and to provision Runners within their GCP projects. - -## Decision - -Rely primarily on [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation) -to manage authorization across the platform. This enables a GitLab user or a -group / project owner to authenticate within GCP via OAuth and have the ability -to assume / configure IAM roles in GCP based on their GitLab user attributes. diff --git a/doc/architecture/blueprints/gcp_integration/index.md b/doc/architecture/blueprints/gcp_integration/index.md deleted file mode 100644 index ec5412442e6..00000000000 --- a/doc/architecture/blueprints/gcp_integration/index.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -status: proposed -creation-date: "2023-10-26" -authors: [ "@sgoldstein" ] -coach: "@jessieay" -approvers: [] -owning-stage: "~devops::verify" -participating-stages: ["~devops::verify", "~devops::package", "~devops::govern"] ---- - -<!-- Blueprints often contain forward-looking statements --> -<!-- vale gitlab.FutureTense = NO --> - -# GCP Integration - -## Summary - -GitLab and Google Cloud Platform (GCP) provide complementary tooling which we are integrating via our [partnership](https://about.gitlab.com/blog/2023/08/29/gitlab-google-partnership-s3c/). - -As phase 1 of this integration, we plan to: - -1. Authentication/Authorization Integration. Onboarding, account-linking and permission management between GitLab and GCP Identities. -1. Continuous Integration. Provision Runners into linked GCP projects. -1. Artifact Management. Package Registry integration with Google Artifact Registry. - -This design doc intends to capture the architecture for this initial integration phase. It should answer the following: - -1. How users, permissions, and service accounts managed between GitLab and GCP identities -1. How to automatically provision GitLab Runners on Google Cloud Compute (GCE) in GCP customer project via Terraform, with or without VPC configured -1. How to automatically push containers from GitLab CI to Google Artifact Registry, view those containers in GitLab, and trace to builds. - -NOTE: -GitLab package team has begun work on architecture design in [Google Artifact Registry Integration](../google_artifact_registry_integration/index.md) - -## Decisions - -- [ADR-001: Do not store GCP credentials in GitLab](decisions/001_no_credentials.md) - -## Proposal - -### Authentication/Authorization - -Rely primarily on [Workload Identity Federation](https://cloud.google.com/iam/docs/workload-identity-federation) to manage authorization across the platform. This enables a GitLab user to authenticate within GCP via OAuth and have the ability to assume IAM roles in GCP based on their GitLab user attributes. - -#### Workload Identity Pool Provider creation in GCP - -As a GCP and GitLab customer (GitLab Group Owner), I would like to create a Workload Identity Pool and Provider to allow GitLab users to authenticate with GCP when executing CI workloads that utilize GCP services. - -Steps: - - 1. Customer navigates to GitLab → Operate → Google Cloud - 1. Customer authenticates with GCP through an OAuth flow, if the customer is not already authenticated. - 1. Creates a GCP Workload Identity Pool and Provider with OpenID Connect configuration in customers GCP Project, by providing the GCP project number at creation time. - 1. There is an [API to go from projectNum->projectID and back](https://cloud.google.com/resource-manager/reference/rest/v3/projects/get). - -#### GCP resource policy creation in GitLab for Workload Identity Provider - -As a GCP customer, I would like to authorize GitLab’s use of my GCP resources for the purpose of the GitLab - GCP Integration: - -Steps: - -1. Customer navigates to GitLab → Operate → Google Cloud -1. Customer authenticates with GCP through an OAuth flow, if the customer is not already authenticated. -1. Customer selects previously created Workload Identity Provider and adds applicable resource policies by specifying the: - 1. GCP project number - 1. One or more GitLab OpenID claims and associated values to use for authenticating a workload - 1. One or more GCP resources IAM roles the authenticated workload will be authorized to use -1. Customer is presented with a prompt asking them to confirm that they wish to proceed in creating this GCP resource policy - 1. If a customer confirms, a policy for the Workload Identity Provider is created in the specified GCP project. -1. If additional GCP resource policies are required, the customer can repeat steps 2 and 3. - -NOTE: -The user should also be able to edit (claims, values and IAM roles) and delete GCP resources policies. - -#### User to User authentication linkage - -As an existing GitLab user, I can use OAuth to link my existing GCP account so that I can seamlessly exchange data between the two - -This will be used for the following use cases: - - 1. In Artifact Registry where the individual user can see artifacts in GCP - 1. Creation of workload identity federation pools - 1. List, pull and push images to Artifact Registry from GitLab - -Also applies in the reverse direction. Existing GCP users can OAuth into GitLab and link their accounts. - -#### Runner Provisioning - -TBA - -#### GitLab CI to Google Artifact Registry Authn/Authz - -TBA - -## Design and implementation details diff --git a/lib/gitlab/analytics/cycle_analytics/request_params.rb b/lib/gitlab/analytics/cycle_analytics/request_params.rb index cea25ba2db4..0c4a0afa1d5 100644 --- a/lib/gitlab/analytics/cycle_analytics/request_params.rb +++ b/lib/gitlab/analytics/cycle_analytics/request_params.rb @@ -203,7 +203,8 @@ module Gitlab def validate_date_range return if created_after.nil? || created_before.nil? - if (created_before - created_after) > MAX_RANGE_DAYS + time_period = created_before.at_beginning_of_day - created_after.at_beginning_of_day + if time_period > MAX_RANGE_DAYS errors.add(:created_after, s_('CycleAnalytics|The given date range is larger than 180 days')) end end diff --git a/package.json b/package.json index cc9c846c800..cf61540faca 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "@gitlab/favicon-overlay": "2.0.0", "@gitlab/fonts": "^1.3.0", "@gitlab/svgs": "3.69.0", - "@gitlab/ui": "67.3.0", + "@gitlab/ui": "67.3.3", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20231004090414", "@mattiasbuelens/web-streams-adapter": "^0.1.0", diff --git a/qa/qa/mobile/page/main/menu.rb b/qa/qa/mobile/page/main/menu.rb index 73d3b9f7982..9bd2fbccbbb 100644 --- a/qa/qa/mobile/page/main/menu.rb +++ b/qa/qa/mobile/page/main/menu.rb @@ -22,10 +22,10 @@ module QA end def open_mobile_menu - if has_no_element?(:user_avatar_content) + if has_no_element?('user-avatar-content') Support::Retrier.retry_until do click_element(:mobile_navbar_button) - has_element?(:user_avatar_content) + has_element?('user-avatar-content') end end end diff --git a/qa/qa/page/component/access_tokens.rb b/qa/qa/page/component/access_tokens.rb index c80df535aba..3f411c05587 100644 --- a/qa/qa/page/component/access_tokens.rb +++ b/qa/qa/page/component/access_tokens.rb @@ -15,12 +15,12 @@ module QA end base.view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do - element :expiry_date_field + element 'expiry-date-field' end base.view 'app/views/shared/access_tokens/_form.html.haml' do - element :access_token_name_field - element :create_token_button + element 'access-token-name-field' + element 'create-token-button' end base.view 'app/views/shared/tokens/_scopes_form.html.haml' do @@ -28,16 +28,16 @@ module QA end base.view 'app/assets/javascripts/access_tokens/components/new_access_token_app.vue' do - element :access_token_section - element :created_access_token_field + element 'access-token-section' + element 'created-access-token-field' end base.view 'app/assets/javascripts/vue_shared/components/form/input_copy_toggle_visibility.vue' do - element :toggle_visibility_button + element 'toggle-visibility-button' end base.view 'app/assets/javascripts/access_tokens/components/access_token_table_app.vue' do - element :revoke_button + element 'revoke-button' end base.view 'app/views/profiles/personal_access_tokens/index.html.haml' do @@ -62,7 +62,7 @@ module QA end def fill_token_name(name) - fill_element(:access_token_name_field, name) + fill_element('access-token-name-field', name) end def check_api @@ -70,12 +70,12 @@ module QA end def click_create_token_button - click_element(:create_token_button) + click_element('create-token-button') end def created_access_token - within_element(:access_token_section) do - find_element(:created_access_token_field).value + within_element('access-token-section') do + find_element('created-access-token-field').value end end @@ -87,7 +87,7 @@ module QA raise "Expiry date must be in YYYY-MM-DD format" end - fill_element(:expiry_date_field, date) + fill_element('expiry-date-field', date) end def has_token_row_for_name?(token_name) @@ -100,7 +100,7 @@ module QA def revoke_first_token_with_name(token_name) within first_token_row_for_name(token_name) do - click_element(:revoke_button) + click_element('revoke-button') end click_confirmation_ok_button diff --git a/qa/qa/page/component/confirm_modal.rb b/qa/qa/page/component/confirm_modal.rb index 26d06ecaa22..fbc3cfa9aed 100644 --- a/qa/qa/page/component/confirm_modal.rb +++ b/qa/qa/page/component/confirm_modal.rb @@ -15,24 +15,24 @@ module QA end base.view 'app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue' do - element :confirm_danger_modal_button - element :confirm_danger_field + element 'confirm-danger-modal-button' + element 'confirm-danger-field' end end def fill_confirmation_text(text) - fill_element(:confirm_danger_field, text) + fill_element('confirm-danger-field', text) end def wait_for_confirm_button_enabled wait_until(reload: false) do - !find_element(:confirm_danger_modal_button).disabled? + !find_element('confirm-danger-modal-button').disabled? end end def confirm_transfer wait_for_confirm_button_enabled - click_element(:confirm_danger_modal_button) + click_element('confirm-danger-modal-button') end def click_confirmation_ok_button diff --git a/qa/qa/page/component/members/invite_members_modal.rb b/qa/qa/page/component/members/invite_members_modal.rb index b9d0b382ba1..66f5cef87f4 100644 --- a/qa/qa/page/component/members/invite_members_modal.rb +++ b/qa/qa/page/component/members/invite_members_modal.rb @@ -12,37 +12,37 @@ module QA super base.view 'app/assets/javascripts/invite_members/components/invite_modal_base.vue' do - element :invite_button - element :access_level_dropdown + element 'invite-modal-submit' + element 'access-level-dropdown' element 'invite-modal' end base.view 'app/assets/javascripts/invite_members/components/members_token_select.vue' do - element :members_token_select_input + element 'members-token-select-input' end base.view 'app/assets/javascripts/invite_members/components/invite_group_trigger.vue' do - element :invite_a_group_button + element 'invite-a-group-button' end - base.view 'app/assets/javascripts/invite_members/constants.js' do - element :invite_members_button + base.view 'app/assets/javascripts/invite_members/components/invite_members_trigger.vue' do + element 'invite-members-button' end end def open_invite_members_modal - click_element :invite_members_button + click_element 'invite-members-button' end def open_invite_group_modal - click_element :invite_a_group_button + click_element 'invite-a-group-button' end def add_member(username, access_level = 'Developer', refresh_page: true) open_invite_members_modal within_element('invite-modal') do - fill_element(:members_token_select_input, username) + fill_element('members-token-select-input', username) Support::WaitForRequests.wait_for_requests click_button(username, match: :prefer_exact) set_access_level(access_level) @@ -68,7 +68,7 @@ module QA end def send_invite(refresh = false) - click_element :invite_button + click_element 'invite-modal-submit' Support::WaitForRequests.wait_for_requests page.refresh if refresh end @@ -77,7 +77,7 @@ module QA def set_access_level(access_level) # Guest option is selected by default, skipping these steps if desired option is 'Guest' - select_element(:access_level_dropdown, access_level) unless access_level == 'Guest' + select_element('access-level-dropdown', access_level) unless access_level == 'Guest' end end end diff --git a/qa/qa/page/group/settings/group_deploy_tokens.rb b/qa/qa/page/group/settings/group_deploy_tokens.rb index c1c3303113b..c76c910e857 100644 --- a/qa/qa/page/group/settings/group_deploy_tokens.rb +++ b/qa/qa/page/group/settings/group_deploy_tokens.rb @@ -6,58 +6,58 @@ module QA module Settings class GroupDeployTokens < Page::Base view 'app/views/shared/deploy_tokens/_form.html.haml' do - element :deploy_token_name_field - element :deploy_token_expires_at_field - element :deploy_token_read_repository_checkbox - element :deploy_token_read_package_registry_checkbox - element :deploy_token_read_registry_checkbox - element :deploy_token_write_package_registry_checkbox - element :create_deploy_token_button + element 'deploy-token-name-field' + element 'deploy-token-expires-at-field' + element 'deploy-token-read-repository-checkbox' + element 'deploy-token-read-package-registry-checkbox' + element 'deploy-token-read-registry-checkbox' + element 'deploy-token-write-package-registry-checkbox' + element 'create-deploy-token-button' end view 'app/views/shared/deploy_tokens/_new_deploy_token.html.haml' do - element :created_deploy_token_container - element :deploy_token_user_field - element :deploy_token_field + element 'created-deploy-token-container' + element 'deploy-token-user-field' + element 'deploy-token-field' end def fill_token_name(name) - fill_element(:deploy_token_name_field, name) + fill_element('deploy-token-name-field', name) end def fill_token_expires_at(expires_at) - fill_element(:deploy_token_expires_at_field, expires_at.to_s + "\n") + fill_element('deploy-token-expires-at-field', expires_at.to_s + "\n") end def fill_scopes(read_repository: false, read_registry: false, read_package_registry: false, write_package_registry: false ) - check_element(:deploy_token_read_repository_checkbox, true) if read_repository - check_element(:deploy_token_read_package_registry_checkbox, true) if read_package_registry - check_element(:deploy_token_read_registry_checkbox, true) if read_registry - check_element(:deploy_token_write_package_registry_checkbox, true) if write_package_registry + check_element('deploy-token-read-repository-checkbox', true) if read_repository + check_element('deploy-token-read-package-registry-checkbox', true) if read_package_registry + check_element('deploytoken-read-registry-checkbox', true) if read_registry + check_element('deploy-token-write-package-registry-checkbox', true) if write_package_registry end def add_token - click_element(:create_deploy_token_button) + click_element('create-deploy-token-button') end def token_username within_new_project_deploy_token do - find_element(:deploy_token_user_field).value + find_element('deploy-token-user-field').value end end def token_password within_new_project_deploy_token do - find_element(:deploy_token_field).value + find_element('deploy-token-field').value end end private def within_new_project_deploy_token(&block) - has_element?(:created_deploy_token_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) + has_element?('created-deploy-token-container', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) - within_element(:created_deploy_token_container, &block) + within_element('created-deploy-token-container', &block) end end end diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb index 1fd0b5b453c..a1c496a1f57 100644 --- a/qa/qa/page/main/login.rb +++ b/qa/qa/page/main/login.rb @@ -7,55 +7,55 @@ module QA include Layout::Flash view 'app/views/devise/passwords/edit.html.haml' do - element :password_field - element :password_confirmation_field - element :change_password_button + element 'password-field' + element 'password-confirmation-field' + element 'change-password-button' end view 'app/views/devise/sessions/new.html.haml' do - element :register_link + element 'register-link' end view 'app/views/devise/sessions/_new_base.html.haml' do - element :login_field - element :password_field - element :sign_in_button + element 'username-field' + element 'password-field' + element 'sign-in-button' end view 'app/views/devise/sessions/_new_ldap.html.haml' do - element :username_field - element :password_field - element :sign_in_button + element 'username-field' + element 'password-field' + element 'sign-in-button' end view 'app/views/devise/shared/_tabs_ldap.html.haml' do - element :ldap_tab - element :standard_tab - element :register_tab + element 'ldap-tab' + element 'standard-tab' + element 'register-tab' end view 'app/views/devise/shared/_tab_single.html.haml' do - element :sign_in_tab + element 'sign-in-tab' end view 'app/helpers/auth_helper.rb' do - element :saml_login_button - element :github_login_button - element :oidc_login_button - element :gitlab_oauth_login_button - element :facebook_login_button + element 'saml-login-button' + element 'github-login-button' + element 'oidc-login-button' + element 'gitlab-oauth-login-button' + element 'facebook-login-button' end view 'app/views/layouts/devise.html.haml' do - element :login_page, required: true + element 'login-page', required: true end def can_sign_in? - has_element?(:sign_in_button) + has_element?('sign-in-button') end def on_login_page? - has_element?(:login_page, wait: 0) + has_element?('login-page', wait: 0) end def sign_in_using_credentials(user: nil, skip_page_validation: false) @@ -98,9 +98,9 @@ module QA switch_to_ldap_tab - fill_element :username_field, user.ldap_username - fill_element :password_field, user.ldap_password - click_element :sign_in_button + fill_element 'username-field', user.ldap_username + fill_element 'password-field', user.ldap_password + click_element 'sign-in-button' end Page::Main::Menu.perform(&:signed_in?) @@ -130,15 +130,15 @@ module QA end def has_sign_in_tab?(wait: Capybara.default_max_wait_time) - has_element?(:sign_in_tab, wait: wait) + has_element?('sign-in-tab', wait: wait) end def has_ldap_tab? - has_element?(:ldap_tab) + has_element?('ldap-tab') end def has_standard_tab? - has_element?(:standard_tab) + has_element?('standard-tab') end def sign_in_tab? @@ -162,45 +162,45 @@ module QA end def switch_to_sign_in_tab - click_element :sign_in_tab + click_element 'sign-in-tab' end def switch_to_register_page set_initial_password_if_present - click_element :register_link + click_element 'register-link' end def switch_to_ldap_tab - click_element :ldap_tab + click_element 'ldap-tab' end def switch_to_standard_tab - click_element :standard_tab + click_element 'standard-tab' end def sign_in_with_github set_initial_password_if_present - click_element :github_login_button + click_element 'github-login-button' end def sign_in_with_facebook set_initial_password_if_present - click_element :facebook_login_button + click_element 'facebook-login-button' end def sign_in_with_saml set_initial_password_if_present - click_element :saml_login_button + click_element 'saml-login-button' end def sign_in_with_gitlab_oidc set_initial_password_if_present - click_element :oidc_login_button + click_element 'oidc-login-button' end def sign_in_with_gitlab_oauth set_initial_password_if_present - click_element :gitlab_oauth_login_button + click_element 'gitlab-oauth-login-button' end def sign_out_and_sign_in_as(user:) @@ -233,7 +233,7 @@ module QA click_accept_all_cookies if Runtime::Env.running_on_dot_com? && has_accept_all_cookies_button? - click_element :sign_in_button + click_element 'sign-in-button' Support::WaitForRequests.wait_for_requests @@ -254,16 +254,16 @@ module QA end def fill_in_credential(user) - fill_element :login_field, user.username - fill_element :password_field, user.password + fill_element 'username-field', user.username + fill_element 'password-field', user.password end def set_initial_password_if_present return unless has_content?('Change your password') - fill_element :password_field, Runtime::User.password - fill_element :password_confirmation_field, Runtime::User.password - click_element :change_password_button + fill_element 'password-field', Runtime::User.password + fill_element 'password-confirmation-field', Runtime::User.password + click_element 'change-password-button' end end end diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb index 73d48c6fcbf..af446291b3c 100644 --- a/qa/qa/page/main/menu.rb +++ b/qa/qa/page/main/menu.rb @@ -16,9 +16,9 @@ module QA view 'app/assets/javascripts/super_sidebar/components/user_menu.vue' do element 'user-dropdown', required: !Runtime::Env.phone_layout? - element :user_avatar_content, required: !Runtime::Env.phone_layout? + element 'user-avatar-content', required: !Runtime::Env.phone_layout? element :sign_out_link - element :edit_profile_link + element 'edit-profile-link' end view 'app/assets/javascripts/super_sidebar/components/user_menu_profile_item.vue' do @@ -118,7 +118,7 @@ module QA has_element?('user-profile-link', text: /#{user.username}/) end # we need to close user menu because plain user link check will leave it open - click_element :user_avatar_content if has_element?('user-profile-link', wait: 0) + click_element 'user-avatar-content' if has_element?('user-profile-link', wait: 0) end def not_signed_in? @@ -150,7 +150,7 @@ module QA def click_edit_profile_link retry_until(reload: false) do within_user_menu do - click_element(:edit_profile_link) + click_element('edit-profile-link') end has_text?('User Settings') @@ -164,11 +164,11 @@ module QA end def has_personal_area?(wait: Capybara.default_max_wait_time) - has_element?(:user_avatar_content, wait: wait) + has_element?('user-avatar-content', wait: wait) end def has_no_personal_area?(wait: Capybara.default_max_wait_time) - has_no_element?(:user_avatar_content, wait: wait) + has_no_element?('user-avatar-content', wait: wait) end def click_stop_impersonation_link @@ -189,7 +189,7 @@ module QA def within_user_menu(&block) within_element(:navbar) do - click_element :user_avatar_content unless has_element?('user-profile-link', wait: 1) + click_element 'user-avatar-content' unless has_element?('user-profile-link', wait: 1) within_element('user-dropdown', &block) end diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb index 2b1a9ab2b6a..2ac99df905a 100644 --- a/qa/qa/page/main/oauth.rb +++ b/qa/qa/page/main/oauth.rb @@ -5,7 +5,7 @@ module QA module Main class OAuth < Page::Base view 'app/views/doorkeeper/authorizations/new.html.haml' do - element :authorization_button + element 'authorization_button' end def needs_authorization? @@ -13,7 +13,7 @@ module QA end def authorize! - click_element :authorization_button + click_element 'authorization_button' end end end diff --git a/qa/qa/page/main/terms.rb b/qa/qa/page/main/terms.rb index 24f6b03549b..1443c5b56f3 100644 --- a/qa/qa/page/main/terms.rb +++ b/qa/qa/page/main/terms.rb @@ -5,17 +5,17 @@ module QA module Main class Terms < Page::Base view 'app/views/layouts/terms.html.haml' do - element :user_avatar_content, required: true + element 'user-avatar-content', required: true end view 'app/assets/javascripts/terms/components/app.vue' do - element :terms_content, required: true + element 'terms-content', required: true - element :accept_terms_button + element 'accept-terms-button' end def accept_terms - click_element :accept_terms_button, Page::Main::Menu + click_element 'accept-terms-button', Page::Main::Menu end end end diff --git a/qa/qa/page/main/two_factor_auth.rb b/qa/qa/page/main/two_factor_auth.rb index 003bd8dd1b1..186027900ca 100644 --- a/qa/qa/page/main/two_factor_auth.rb +++ b/qa/qa/page/main/two_factor_auth.rb @@ -5,16 +5,16 @@ module QA module Main class TwoFactorAuth < Page::Base view 'app/views/devise/sessions/two_factor.html.haml' do - element :verify_code_button - element :two_fa_code_field + element 'verify-code-button' + element 'two-fa-code-field' end def click_verify_code_button - click_element :verify_code_button + click_element 'verify-code-button' end def set_2fa_code(code) - fill_element(:two_fa_code_field, code) + fill_element('two-fa-code-field', code) end end end diff --git a/qa/qa/page/profile/accounts/show.rb b/qa/qa/page/profile/accounts/show.rb index 84a34d1da78..b29c1d9da12 100644 --- a/qa/qa/page/profile/accounts/show.rb +++ b/qa/qa/page/profile/accounts/show.rb @@ -6,24 +6,24 @@ module QA module Accounts class Show < Page::Base view 'app/views/profiles/accounts/show.html.haml' do - element :delete_account_button, required: true - element :enable_2fa_button + element 'delete-account-button', required: true + element 'enable-2fa-button' end view 'app/assets/javascripts/profile/account/components/delete_account_modal.vue' do - element :password_confirmation_field - element :confirm_delete_account_button + element 'password-confirmation-field' + element 'confirm-delete-account-button' end def click_enable_2fa_button - click_element(:enable_2fa_button) + click_element('enable-2fa-button') end def delete_account(password) - click_element(:delete_account_button) + click_element('delete-account-button') - find_element(:password_confirmation_field).set password - click_element(:confirm_delete_account_button) + find_element('password-confirmation-field').set password + click_element('confirm-delete-account-button') end end end diff --git a/qa/qa/page/profile/ssh_keys.rb b/qa/qa/page/profile/ssh_keys.rb index 2990ba6a4ac..c9ec056261a 100644 --- a/qa/qa/page/profile/ssh_keys.rb +++ b/qa/qa/page/profile/ssh_keys.rb @@ -11,7 +11,7 @@ module QA end view 'app/assets/javascripts/access_tokens/components/expires_at_field.vue' do - element :expiry_date_field + element 'expiry-date-field' end view 'app/helpers/ssh_keys_helper.rb' do @@ -31,7 +31,7 @@ module QA # Expire in 2 days just in case the key is created just before midnight fill_expiry_date(Date.today + 2) # Close the datepicker - find_element(:expiry_date_field).find('input').send_keys(:enter) + find_element('expiry-date-field').find('input').send_keys(:enter) click_element(:add_key_button) end @@ -44,7 +44,7 @@ module QA raise "Expiry date must be in YYYY-MM-DD format" end - fill_element(:expiry_date_field, date) + fill_element('expiry-date-field', date) end def remove_key(title) diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb index 2add02b5c48..980d4a74a8c 100644 --- a/qa/qa/page/profile/two_factor_auth.rb +++ b/qa/qa/page/profile/two_factor_auth.rb @@ -5,61 +5,61 @@ module QA module Profile class TwoFactorAuth < Page::Base view 'app/assets/javascripts/pages/profiles/two_factor_auths/index.js' do - element :configure_it_later_button + element 'configure-it-later-button' end view 'app/views/profiles/two_factor_auths/show.html.haml' do - element :otp_secret_content - element :pin_code_field - element :current_password_field - element :register_2fa_app_button + element 'otp-secret-content' + element 'pin-code-field' + element 'current-password-field' + element 'register-2fa-app-button' end view 'app/assets/javascripts/authentication/two_factor_auth/components/recovery_codes.vue' do - element :proceed_button - element :copy_button - element :codes_content - element :code_content + element 'proceed-button' + element 'copy-button' + element 'recovery-codes' + element 'code-content' end def click_configure_it_later_button # TO DO: Investigate why button does not appear sometimes: # https://gitlab.com/gitlab-org/gitlab/-/issues/382698 page.refresh - return unless has_element?(:configure_it_later_button, wait: 60) + return unless has_element?('configure-it-later-button', wait: 60) - click_element :configure_it_later_button + click_element 'configure-it-later-button' wait_until(max_duration: 10, message: "Waiting for create a group page") do has_text?("Welcome to GitLab") && has_text?("Create a group") end end def otp_secret_content - find_element(:otp_secret_content).text.gsub('Key:', '').delete(' ') + find_element('otp-secret-content').text.gsub('Key:', '').delete(' ') end def set_pin_code(pin_code) - fill_element(:pin_code_field, pin_code) + fill_element('pin-code-field', pin_code) end def set_current_password(password) - fill_element(:current_password_field, password) + fill_element('current-password-field', password) end def click_register_2fa_app_button - click_element :register_2fa_app_button + click_element 'register-2fa-app-button' end def recovery_codes - code_elements = within_element(:codes_content) do - all_elements(:code_content, minimum: 1) + code_elements = within_element('recovery-codes') do + all_elements('code-content', minimum: 1) end code_elements.map { |code_content| code_content.text } end def click_copy_and_proceed - click_element :copy_button - click_element :proceed_button + click_element 'copy-button' + click_element 'proceed-button' end end end diff --git a/qa/qa/page/project/settings/deploy_tokens.rb b/qa/qa/page/project/settings/deploy_tokens.rb index cf25f4a0568..a1ba270a42f 100644 --- a/qa/qa/page/project/settings/deploy_tokens.rb +++ b/qa/qa/page/project/settings/deploy_tokens.rb @@ -6,74 +6,74 @@ module QA module Settings class DeployTokens < Page::Base view 'app/views/shared/deploy_tokens/_form.html.haml' do - element :deploy_token_name_field - element :deploy_token_expires_at_field - element :deploy_token_read_repository_checkbox - element :deploy_token_read_package_registry_checkbox - element :deploy_token_write_package_registry_checkbox - element :deploy_token_read_registry_checkbox - element :deploy_token_write_registry_checkbox - element :create_deploy_token_button + element 'deploy-token-name-field' + element 'deploy-token-expires-at-field' + element 'deploy-token-read-repository-checkbox' + element 'deploy-token-read-package-registry-checkbox' + element 'deploy-token-write-package-registry-checkbox' + element 'deploy-token-read-registry-checkbox' + element 'deploy-token-write-registry-checkbox' + element 'create-deploy-token-button' end view 'app/views/shared/deploy_tokens/_new_deploy_token.html.haml' do - element :created_deploy_token_container - element :deploy_token_user_field - element :deploy_token_field + element 'created-deploy-token-container' + element 'deploy-token-user-field' + element 'deploy-token-field' end def fill_token_name(name) - fill_element(:deploy_token_name_field, name) + fill_element('deploy-token-name-field', name) end def fill_token_expires_at(expires_at) - fill_element(:deploy_token_expires_at_field, expires_at.to_s + "\n") + fill_element('deploy-token-expires-at-field', expires_at.to_s + "\n") end def fill_scopes(scopes) if scopes.include? :read_repository - check_element(:deploy_token_read_repository_checkbox, true) + check_element('deploy-token-read-repository-checkbox', true) end if scopes.include? :read_package_registry - check_element(:deploy_token_read_package_registry_checkbox, true) + check_element('deploy-token-read-package-registry-checkbox', true) end if scopes.include? :write_package_registry - check_element(:deploy_token_write_package_registry_checkbox, true) + check_element('deploy-token-write-package-registry-checkbox', true) end if scopes.include? :read_registry - check_element(:deploy_token_read_registry_checkbox, true) + check_element('deploy-token-read-registry-checkbox', true) end if scopes.include? :write_registry - check_element(:deploy_token_write_registry_checkbox, true) + check_element('deploy-token-write-registry-checkbox', true) end end def add_token - click_element(:create_deploy_token_button) + click_element('create-deploy-token-button') end def token_username within_new_project_deploy_token do - find_element(:deploy_token_user_field).value + find_element('deploy-token-user-field').value end end def token_password within_new_project_deploy_token do - find_element(:deploy_token_field).value + find_element('deploy-token-field').value end end private def within_new_project_deploy_token - has_element?(:created_deploy_token_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) + has_element?('created-deploy-token-container', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME) - within_element(:created_deploy_token_container) do + within_element('created-deploy-token-container') do yield end end diff --git a/spec/features/alert_management/alert_details_spec.rb b/spec/features/alert_management/alert_details_spec.rb index 66b7a9ca46c..7fa9bccbece 100644 --- a/spec/features/alert_management/alert_details_spec.rb +++ b/spec/features/alert_management/alert_details_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Alert details', :js, feature_category: :incident_management do let_it_be(:project) { create(:project) } - let_it_be(:developer) { create(:user, :no_super_sidebar) } + let_it_be(:developer) { create(:user) } let_it_be(:alert) { create(:alert_management_alert, project: project, status: 'triggered', title: 'Alert') } before_all do diff --git a/spec/features/alert_management/alert_management_list_spec.rb b/spec/features/alert_management/alert_management_list_spec.rb index cc54af249e1..058447b3be3 100644 --- a/spec/features/alert_management/alert_management_list_spec.rb +++ b/spec/features/alert_management/alert_management_list_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe 'Alert Management index', :js, feature_category: :incident_management do let_it_be(:project) { create(:project) } - let_it_be(:developer) { create(:user, :no_super_sidebar) } + let_it_be(:developer) { create(:user) } before_all do project.add_developer(developer) @@ -22,7 +22,7 @@ RSpec.describe 'Alert Management index', :js, feature_category: :incident_manage expect(page).to have_content('Alerts') expect(page).to have_content('Surface alerts in GitLab') expect(page).not_to have_selector('.gl-table') - page.within('.layout-page') do + page.within('.content-wrapper') do expect(page).not_to have_css('[data-testid="search-icon"]') end end @@ -31,7 +31,7 @@ RSpec.describe 'Alert Management index', :js, feature_category: :incident_manage it 'renders correctly' do expect(page).to have_content('Alerts') expect(page).to have_selector('.gl-table') - page.within('.layout-page') do + page.within('.content-wrapper') do expect(page).to have_css('[data-testid="search-icon"]') end end diff --git a/spec/features/projects/members/manage_groups_spec.rb b/spec/features/projects/members/manage_groups_spec.rb index 63ff1ba8455..3a67879e33d 100644 --- a/spec/features/projects/members/manage_groups_spec.rb +++ b/spec/features/projects/members/manage_groups_spec.rb @@ -228,6 +228,6 @@ RSpec.describe 'Project > Members > Manage groups', :js, feature_category: :grou end def invite_group_selector - 'button[data-test-id="invite-group-button"]' + 'button[data-testid="invite-a-group-button"]' end end diff --git a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap index 2bd2b17a12d..7785693ff2a 100644 --- a/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap +++ b/spec/frontend/access_tokens/components/__snapshots__/expires_at_field_spec.js.snap @@ -11,7 +11,7 @@ exports[`~/access_tokens/components/expires_at_field should render datepicker wi arialabel="" autocomplete="" container="" - data-qa-selector="expiry_date_field" + data-testid="expiry-date-field" defaultdate="Wed Aug 05 2020 00:00:00 GMT+0000 (Greenwich Mean Time)" displayfield="true" firstday="0" diff --git a/spec/frontend/access_tokens/components/access_token_table_app_spec.js b/spec/frontend/access_tokens/components/access_token_table_app_spec.js index ae767f8b3f5..aa7aad8e93e 100644 --- a/spec/frontend/access_tokens/components/access_token_table_app_spec.js +++ b/spec/frontend/access_tokens/components/access_token_table_app_spec.js @@ -153,7 +153,7 @@ describe('~/access_tokens/components/access_token_table_app', () => { let button = cells.at(6).findComponent(GlButton); expect(button.attributes()).toMatchObject({ 'aria-label': __('Revoke'), - 'data-qa-selector': __('revoke_button'), + 'data-testid': 'revoke-button', href: '/-/profile/personal_access_tokens/1/revoke', 'data-confirm': sprintf( __( diff --git a/spec/frontend/access_tokens/components/new_access_token_app_spec.js b/spec/frontend/access_tokens/components/new_access_token_app_spec.js index d51ac638f0e..966a69fa60a 100644 --- a/spec/frontend/access_tokens/components/new_access_token_app_spec.js +++ b/spec/frontend/access_tokens/components/new_access_token_app_spec.js @@ -81,20 +81,6 @@ describe('~/access_tokens/components/new_access_token_app', () => { ); }); - it('input field should contain QA-related selectors', async () => { - const newToken = '12345'; - await triggerSuccess(newToken); - - expect(findGlAlertError().exists()).toBe(false); - - const inputAttributes = wrapper - .findByLabelText(sprintf(__('Your new %{accessTokenType}'), { accessTokenType })) - .attributes(); - expect(inputAttributes).toMatchObject({ - 'data-qa-selector': 'created_access_token_field', - }); - }); - it('should render an info alert', async () => { await triggerSuccess(); diff --git a/spec/frontend/invite_members/components/invite_members_trigger_spec.js b/spec/frontend/invite_members/components/invite_members_trigger_spec.js index 58c40a49b3c..f14d24538d8 100644 --- a/spec/frontend/invite_members/components/invite_members_trigger_spec.js +++ b/spec/frontend/invite_members/components/invite_members_trigger_spec.js @@ -4,7 +4,6 @@ import InviteMembersTrigger from '~/invite_members/components/invite_members_tri import eventHub from '~/invite_members/event_hub'; import { TRIGGER_ELEMENT_BUTTON, - TRIGGER_DEFAULT_QA_SELECTOR, TRIGGER_ELEMENT_WITH_EMOJI, TRIGGER_ELEMENT_DROPDOWN_WITH_EMOJI, TRIGGER_ELEMENT_DISCLOSURE_DROPDOWN, @@ -66,18 +65,6 @@ describe.each(triggerItems)('with triggerElement as %s', (triggerItem) => { expect(findButton().text()).toBe(displayText); }); - - it('uses the default qa selector value', () => { - createComponent(); - - expect(findButton().attributes('data-qa-selector')).toBe(TRIGGER_DEFAULT_QA_SELECTOR); - }); - - it('sets the qa selector value', () => { - createComponent({ qaSelector: '_qaSelector_' }); - - expect(findButton().attributes('data-qa-selector')).toBe('_qaSelector_'); - }); }); describe('clicking the link', () => { diff --git a/spec/frontend/invite_members/components/invite_modal_base_spec.js b/spec/frontend/invite_members/components/invite_modal_base_spec.js index e70c83a424e..45d2afb0eac 100644 --- a/spec/frontend/invite_members/components/invite_modal_base_spec.js +++ b/spec/frontend/invite_members/components/invite_modal_base_spec.js @@ -91,7 +91,6 @@ describe('InviteModalBase', () => { const actionButton = findActionButton(); expect(actionButton.text()).toBe(INVITE_BUTTON_TEXT); - expect(actionButton.attributes('data-qa-selector')).toBe('invite_button'); expect(actionButton.props()).toMatchObject({ variant: 'confirm', diff --git a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js index 53218d794c7..b825a578cee 100644 --- a/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js +++ b/spec/frontend/vue_shared/components/confirm_danger/confirm_danger_modal_spec.js @@ -19,7 +19,7 @@ describe('Confirm Danger Modal', () => { const findModal = () => wrapper.findComponent(GlModal); const findConfirmationPhrase = () => wrapper.findByTestId('confirm-danger-phrase'); - const findConfirmationInput = () => wrapper.findByTestId('confirm-danger-input'); + const findConfirmationInput = () => wrapper.findByTestId('confirm-danger-field'); const findDefaultWarning = () => wrapper.findByTestId('confirm-danger-warning'); const findAdditionalMessage = () => wrapper.findByTestId('confirm-danger-message'); const findPrimaryAction = () => findModal().props('actionPrimary'); diff --git a/spec/models/integrations/integration_list_spec.rb b/spec/models/integrations/integration_list_spec.rb index b7ccbcecf6b..4bb7b100bc0 100644 --- a/spec/models/integrations/integration_list_spec.rb +++ b/spec/models/integrations/integration_list_spec.rb @@ -12,10 +12,10 @@ RSpec.describe Integrations::IntegrationList, feature_category: :integrations do describe '#to_array' do it 'returns array of Integration, columns, and values' do - expect(subject.to_array).to eq([ + expect(subject.to_array).to match_array([ Integration, %w[active category project_id], - [['true', 'common', projects.first.id], ['true', 'common', projects.second.id]] + contain_exactly(['true', 'common', projects.first.id], ['true', 'common', projects.second.id]) ]) end end diff --git a/spec/services/admin/plan_limits/update_service_spec.rb b/spec/services/admin/plan_limits/update_service_spec.rb index e57c234780c..eb9bbcf11aa 100644 --- a/spec/services/admin/plan_limits/update_service_spec.rb +++ b/spec/services/admin/plan_limits/update_service_spec.rb @@ -82,9 +82,9 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Notification limit must be greater than or equal to " \ - "storage_size_limit (Dashboard limit): 5 " \ - "and less than or equal to enforcement_limit: 10"] + expect(response[:message]).to eq [ + "Notification limit must be greater than or equal to the dashboard limit (5)" + ] end end @@ -102,9 +102,9 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Notification limit must be greater than or equal to " \ - "storage_size_limit (Dashboard limit): 5 " \ - "and less than or equal to enforcement_limit: 10"] + expect(response[:message]).to eq [ + "Notification limit must be less than or equal to the enforcement limit (10)" + ] end end @@ -113,8 +113,8 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do before do limits.update!( - storage_size_limit: 12, - notification_limit: 12 + storage_size_limit: 10, + notification_limit: 9 ) end @@ -122,9 +122,9 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Enforcement limit must be greater than " \ - "or equal to storage_size_limit (Dashboard limit): " \ - "12 and greater than or equal to notification_limit: 12"] + expect(response[:message]).to eq [ + "Enforcement limit must be greater than or equal to the dashboard limit (10)" + ] end end @@ -133,7 +133,7 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do before do limits.update!( - storage_size_limit: 10, + storage_size_limit: 9, notification_limit: 10 ) end @@ -142,9 +142,9 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Enforcement limit must be greater than or equal to " \ - "storage_size_limit (Dashboard limit): " \ - "10 and greater than or equal to notification_limit: 10"] + expect(response[:message]).to eq [ + "Enforcement limit must be greater than or equal to the notification limit (10)" + ] end end @@ -162,8 +162,9 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Storage size limit (Dashboard limit) must be less than or " \ - "equal to enforcement_limit: 12 and notification_limit: 10"] + expect(response[:message]).to eq [ + "Dashboard limit must be less than or equal to the notification limit (10)" + ] end end @@ -181,8 +182,45 @@ RSpec.describe Admin::PlanLimits::UpdateService, feature_category: :shared do response = update_plan_limits expect(response[:status]).to eq :error - expect(response[:message]).to eq ["Storage size limit (Dashboard limit) must be less than or " \ - "equal to enforcement_limit: 10 and notification_limit: 11"] + expect(response[:message]).to eq [ + "Dashboard limit must be less than or equal to the enforcement limit (10)" + ] + end + + context 'when enforcement_limit is 0' do + before do + limits.update!( + enforcement_limit: 0 + ) + end + + it 'does not return an error' do + response = update_plan_limits + + expect(response[:status]).to eq :success + end + end + end + end + + context 'when setting limit to unlimited' do + before do + limits.update!( + notification_limit: 10, + storage_size_limit: 10, + enforcement_limit: 10 + ) + end + + [:notification_limit, :enforcement_limit, :storage_size_limit].each do |limit| + context "for #{limit}" do + let(:params) { { limit => 0 } } + + it 'is successful' do + response = update_plan_limits + + expect(response[:status]).to eq :success + end end end end diff --git a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb index 0e7b909fce9..cf539174587 100644 --- a/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb +++ b/spec/support/shared_examples/analytics/cycle_analytics/request_params_examples.rb @@ -45,9 +45,19 @@ RSpec.shared_examples 'unlicensed cycle analytics request params' do end end + context 'when the date range is exactly 180 days' do + before do + params[:created_before] = '2019-06-30' + end + + it 'is valid' do + expect(subject).to be_valid + end + end + context 'when the date range exceeds 180 days' do before do - params[:created_before] = '2019-07-15' + params[:created_before] = '2019-07-01' end it 'is invalid' do diff --git a/yarn.lock b/yarn.lock index 7e4881aacf8..b4ecb9b9f6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1274,10 +1274,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.69.0.tgz#bf76b8ffbe72a783807761a38abe8aaedcfe8c12" integrity sha512-Zu8Fcjhi3Bk26jZOptcD5F4SHWC7/KuAe00NULViCeswKdoda1k19B+9oCSbsbxY7vMoFuD20kiCJdBCpxb3HA== -"@gitlab/ui@67.3.0": - version "67.3.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-67.3.0.tgz#e106c742591595c78282244dcf0632157c81e75f" - integrity sha512-PSb/gpxRMRpZ99glO9u8hqWKA5vn+fKImS+0s1R3OuQOKIY36t4pYVtlZ/HD6eSQQzc92gx3GSpdfDdjPPmI7w== +"@gitlab/ui@67.3.3": + version "67.3.3" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-67.3.3.tgz#11ce618a41288fd64ad0a5768dd517ac830fadc8" + integrity sha512-Gc+HYwB0MdEO0qDboNnDTROpItlWB1rFFqFqe7h/IuqJUHyr2NmUAvhrPINE6f56qa5GcMTDlFazmUUY4kcxCg== dependencies: "@floating-ui/dom" "1.2.9" bootstrap-vue "2.23.1" |