From c5389a77fffa55bd4e40485906f591b0427f539e Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Fri, 2 Jun 2023 10:55:25 +0800 Subject: [PATCH 1/7] Refactor: user profile page to Vue * Add feature flag for user profile refactoring. * Apply feature flag to the mount element. Changelog: added --- app/views/profiles/show.html.haml | 377 +++++++++--------- .../development/user_profile_vue.yml | 8 + 2 files changed, 198 insertions(+), 187 deletions(-) create mode 100644 config/feature_flags/development/user_profile_vue.yml diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 930f4f5c397b2f..be2a4e07189420 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -4,195 +4,198 @@ - gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host - @force_desktop_expanded_sidebar = true -= gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f| - .row.js-search-settings-section - .col-lg-4.profile-settings-sidebar - %h4.gl-mt-0 - = s_("Profiles|Public avatar") - %p - - if @user.avatar? - - if gravatar_enabled? - = s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link } - - else - = s_("Profiles|You can change your avatar here") - - else - - if gravatar_enabled? - = s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link } +- if Feature.enabled?(:user_profile_vue) + .js-user-profile +- else + = gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f| + .row.js-search-settings-section + .col-lg-4.profile-settings-sidebar + %h4.gl-mt-0 + = s_("Profiles|Public avatar") + %p + - if @user.avatar? + - if gravatar_enabled? + = s_("Profiles|You can change your avatar here or remove the current avatar to revert to %{gravatar_link}").html_safe % { gravatar_link: gravatar_link } + - else + = s_("Profiles|You can change your avatar here") - else - = s_("Profiles|You can upload your avatar here") - - if current_appearance&.profile_image_guidelines? - .md - = brand_profile_image_guidelines - .col-lg-8 - .avatar-image - = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do - = render Pajamas::AvatarComponent.new(@user, size: 96, alt: "", class: 'gl-float-left gl-mr-5') - %h5.gl-mt-0= s_("Profiles|Upload new avatar") - .gl-display-flex.gl-align-items-center.gl-my-3 - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-choose-user-avatar-button' }) do - = s_("Profiles|Choose file...") - %span.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.") - = f.file_field :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' - .gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.") - - if @user.avatar? - = render Pajamas::ButtonComponent.new(variant: :danger, - category: :secondary, - href: profile_avatar_path, - button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } }, - method: :delete) do - = s_("Profiles|Remove avatar") - .col-lg-12 - %hr - .row.js-search-settings-section - .col-lg-4.profile-settings-sidebar - %h4.gl-mt-0= s_("Profiles|Current status") - %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.") - .col-lg-8 - #js-user-profile-set-status-form - = f.fields_for :status, @user.status do |status_form| - = status_form.hidden_field :emoji, data: { js_name: 'emoji' } - = status_form.hidden_field :message, data: { js_name: 'message' } - = status_form.hidden_field :availability, data: { js_name: 'availability' } - = status_form.hidden_field :clear_status_after, - value: user_clear_status_at(@user), - data: { js_name: 'clearStatusAfter' } - .col-lg-12 - %hr - .row.user-time-preferences.js-search-settings-section - .col-lg-4.profile-settings-sidebar - %h4.gl-mt-0= s_("Profiles|Time settings") - %p= s_("Profiles|Set your local time zone.") - .col-lg-8 - = f.label :user_timezone, _("Time zone") - .js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } } - .col-lg-12 - %hr - .row.js-search-settings-section - .col-lg-4.profile-settings-sidebar - %h4.gl-mt-0 - = s_("Profiles|Main settings") - %p - = s_("Profiles|This information will appear on your profile.") - - if current_user.ldap_user? - = s_("Profiles|Some options are unavailable for LDAP accounts") - .col-lg-8 - .row - .form-group.gl-form-group.col-md-9.rspec-full-name - = render 'profiles/name', form: f, user: @user - .form-group.gl-form-group.col-md-3 - = f.label :id, s_('Profiles|User ID') - = f.text_field :id, class: 'gl-form-input form-control', readonly: true - .form-group.gl-form-group - = f.label :pronouns, s_('Profiles|Pronouns') - = f.text_field :pronouns, class: 'gl-form-input form-control gl-md-form-input-lg' - %small.form-text.text-gl-muted - = s_("Profiles|Enter your pronouns to let people know how to refer to you.") - .form-group.gl-form-group - = f.label :pronunciation, s_('Profiles|Pronunciation') - = f.text_field :pronunciation, class: 'gl-form-input form-control gl-md-form-input-lg' - %small.form-text.text-gl-muted - = s_("Profiles|Enter how your name is pronounced to help people address you correctly.") - = render_if_exists 'profiles/extra_settings', form: f - = render_if_exists 'profiles/email_settings', form: f - .form-group.gl-form-group - = f.label :skype - = f.text_field :skype, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|username") - .form-group.gl-form-group - = f.label :linkedin - = f.text_field :linkedin, class: 'gl-form-input form-control gl-md-form-input-lg' - %small.form-text.text-gl-muted - = s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename") - .form-group.gl-form-group - = f.label :twitter - = f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username") - .form-group.gl-form-group - - external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page') - - external_accounts_link_start = ''.html_safe % { url: external_accounts_help_url } - - external_accounts_docs_link = s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}').html_safe % { external_accounts_link_start: external_accounts_link_start, external_accounts_link_end: ''.html_safe } - - min_discord_length = 17 - - max_discord_length = 20 - = f.label :discord - = f.text_field :discord, - class: 'gl-form-input form-control gl-md-form-input-lg js-validate-length', - placeholder: s_("Profiles|User ID"), - data: { min_length: min_discord_length, - min_length_message: s_('Profiles|Discord ID is too short (minimum is %{min_length} characters).') % { min_length: min_discord_length }, - max_length: max_discord_length, - max_length_message: s_('Profiles|Discord ID is too long (maximum is %{max_length} characters).') % { max_length: max_discord_length }, - allow_empty: true} - %small.form-text.text-gl-muted - = external_accounts_docs_link + - if gravatar_enabled? + = s_("Profiles|You can upload your avatar here or change it at %{gravatar_link}").html_safe % { gravatar_link: gravatar_link } + - else + = s_("Profiles|You can upload your avatar here") + - if current_appearance&.profile_image_guidelines? + .md + = brand_profile_image_guidelines + .col-lg-8 + .avatar-image + = link_to avatar_icon_for_user(@user, 400), target: '_blank', rel: 'noopener noreferrer' do + = render Pajamas::AvatarComponent.new(@user, size: 96, alt: "", class: 'gl-float-left gl-mr-5') + %h5.gl-mt-0= s_("Profiles|Upload new avatar") + .gl-display-flex.gl-align-items-center.gl-my-3 + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-choose-user-avatar-button' }) do + = s_("Profiles|Choose file...") + %span.gl-ml-3.js-avatar-filename= s_("Profiles|No file chosen.") + = f.file_field :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' + .gl-text-gray-500= s_("Profiles|The maximum file size allowed is 200KB.") + - if @user.avatar? + = render Pajamas::ButtonComponent.new(variant: :danger, + category: :secondary, + href: profile_avatar_path, + button_options: { class: 'gl-mt-3', data: { confirm: s_("Profiles|Avatar will be removed. Are you sure?") } }, + method: :delete) do + = s_("Profiles|Remove avatar") + .col-lg-12 + %hr + .row.js-search-settings-section + .col-lg-4.profile-settings-sidebar + %h4.gl-mt-0= s_("Profiles|Current status") + %p= s_("Profiles|This emoji and message will appear on your profile and throughout the interface.") + .col-lg-8 + #js-user-profile-set-status-form + = f.fields_for :status, @user.status do |status_form| + = status_form.hidden_field :emoji, data: { js_name: 'emoji' } + = status_form.hidden_field :message, data: { js_name: 'message' } + = status_form.hidden_field :availability, data: { js_name: 'availability' } + = status_form.hidden_field :clear_status_after, + value: user_clear_status_at(@user), + data: { js_name: 'clearStatusAfter' } + .col-lg-12 + %hr + .row.user-time-preferences.js-search-settings-section + .col-lg-4.profile-settings-sidebar + %h4.gl-mt-0= s_("Profiles|Time settings") + %p= s_("Profiles|Set your local time zone.") + .col-lg-8 + = f.label :user_timezone, _("Time zone") + .js-timezone-dropdown{ data: { timezone_data: timezone_data.to_json, initial_value: @user.timezone, name: 'user[timezone]' } } + .col-lg-12 + %hr + .row.js-search-settings-section + .col-lg-4.profile-settings-sidebar + %h4.gl-mt-0 + = s_("Profiles|Main settings") + %p + = s_("Profiles|This information will appear on your profile.") + - if current_user.ldap_user? + = s_("Profiles|Some options are unavailable for LDAP accounts") + .col-lg-8 + .row + .form-group.gl-form-group.col-md-9.rspec-full-name + = render 'profiles/name', form: f, user: @user + .form-group.gl-form-group.col-md-3 + = f.label :id, s_('Profiles|User ID') + = f.text_field :id, class: 'gl-form-input form-control', readonly: true + .form-group.gl-form-group + = f.label :pronouns, s_('Profiles|Pronouns') + = f.text_field :pronouns, class: 'gl-form-input form-control gl-md-form-input-lg' + %small.form-text.text-gl-muted + = s_("Profiles|Enter your pronouns to let people know how to refer to you.") + .form-group.gl-form-group + = f.label :pronunciation, s_('Profiles|Pronunciation') + = f.text_field :pronunciation, class: 'gl-form-input form-control gl-md-form-input-lg' + %small.form-text.text-gl-muted + = s_("Profiles|Enter how your name is pronounced to help people address you correctly.") + = render_if_exists 'profiles/extra_settings', form: f + = render_if_exists 'profiles/email_settings', form: f + .form-group.gl-form-group + = f.label :skype + = f.text_field :skype, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|username") + .form-group.gl-form-group + = f.label :linkedin + = f.text_field :linkedin, class: 'gl-form-input form-control gl-md-form-input-lg' + %small.form-text.text-gl-muted + = s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename") + .form-group.gl-form-group + = f.label :twitter + = f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username") + .form-group.gl-form-group + - external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page') + - external_accounts_link_start = ''.html_safe % { url: external_accounts_help_url } + - external_accounts_docs_link = s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}').html_safe % { external_accounts_link_start: external_accounts_link_start, external_accounts_link_end: ''.html_safe } + - min_discord_length = 17 + - max_discord_length = 20 + = f.label :discord + = f.text_field :discord, + class: 'gl-form-input form-control gl-md-form-input-lg js-validate-length', + placeholder: s_("Profiles|User ID"), + data: { min_length: min_discord_length, + min_length_message: s_('Profiles|Discord ID is too short (minimum is %{min_length} characters).') % { min_length: min_discord_length }, + max_length: max_discord_length, + max_length_message: s_('Profiles|Discord ID is too long (maximum is %{max_length} characters).') % { max_length: max_discord_length }, + allow_empty: true} + %small.form-text.text-gl-muted + = external_accounts_docs_link - .form-group.gl-form-group - = f.label :website_url, s_('Profiles|Website url') - = f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com") - .form-group.gl-form-group - = f.label :location, s_('Profiles|Location') - - if @user.read_only_attribute?(:location) - = f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: true + .form-group.gl-form-group + = f.label :website_url, s_('Profiles|Website url') + = f.text_field :website_url, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|https://website.com") + .form-group.gl-form-group + = f.label :location, s_('Profiles|Location') + - if @user.read_only_attribute?(:location) + = f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', readonly: true + %small.form-text.text-gl-muted + = s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) } + - else + = f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|City, country") + .form-group.gl-form-group + = f.label :job_title, s_('Profiles|Job title') + = f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg' + .form-group.gl-form-group + = f.label :organization, s_('Profiles|Organization') + = f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg' + %small.form-text.text-gl-muted + = s_("Profiles|Who you represent or work for.") + .form-group.gl-form-group + = f.label :bio, s_('Profiles|Bio') + = f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250 %small.form-text.text-gl-muted - = s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) } - - else - = f.text_field :location, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|City, country") - .form-group.gl-form-group - = f.label :job_title, s_('Profiles|Job title') - = f.text_field :job_title, class: 'gl-form-input form-control gl-md-form-input-lg' - .form-group.gl-form-group - = f.label :organization, s_('Profiles|Organization') - = f.text_field :organization, class: 'gl-form-input form-control gl-md-form-input-lg' - %small.form-text.text-gl-muted - = s_("Profiles|Who you represent or work for.") - .form-group.gl-form-group - = f.label :bio, s_('Profiles|Bio') - = f.text_area :bio, class: 'gl-form-input gl-form-textarea form-control', rows: 4, maxlength: 250 - %small.form-text.text-gl-muted - = s_("Profiles|Tell us about yourself in fewer than 250 characters.") - %hr - %fieldset.form-group.gl-form-group - %legend.col-form-label.col-form-label - = _('Private profile') - - private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile.") - - private_profile_help_link = link_to sprite_icon('question-o'), help_page_path('user/profile/index.md', anchor: 'make-your-user-profile-page-private') - = f.gitlab_ui_checkbox_component :private_profile, '%{private_profile_label} %{private_profile_help_link}'.html_safe % { private_profile_label: private_profile_label, private_profile_help_link: private_profile_help_link.html_safe } - %fieldset.form-group.gl-form-group - %legend.col-form-label.col-form-label - = s_("Profiles|Private contributions") - = f.gitlab_ui_checkbox_component :include_private_contributions, - s_('Profiles|Include private contributions on your profile'), - help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.") - %fieldset.form-group.gl-form-group - %legend.col-form-label.col-form-label - = s_("Profiles|Achievements") - = f.gitlab_ui_checkbox_component :achievements_enabled, - s_('Profiles|Display achievements on your profile') - .row.js-hide-when-nothing-matches-search - .col-lg-12 - %hr - = f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true - = render Pajamas::ButtonComponent.new(href: user_path(current_user)) do - = s_('TagsPage|Cancel') + = s_("Profiles|Tell us about yourself in fewer than 250 characters.") + %hr + %fieldset.form-group.gl-form-group + %legend.col-form-label.col-form-label + = _('Private profile') + - private_profile_label = s_("Profiles|Don't display activity-related personal information on your profile.") + - private_profile_help_link = link_to sprite_icon('question-o'), help_page_path('user/profile/index.md', anchor: 'make-your-user-profile-page-private') + = f.gitlab_ui_checkbox_component :private_profile, '%{private_profile_label} %{private_profile_help_link}'.html_safe % { private_profile_label: private_profile_label, private_profile_help_link: private_profile_help_link.html_safe } + %fieldset.form-group.gl-form-group + %legend.col-form-label.col-form-label + = s_("Profiles|Private contributions") + = f.gitlab_ui_checkbox_component :include_private_contributions, + s_('Profiles|Include private contributions on your profile'), + help_text: s_("Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information.") + %fieldset.form-group.gl-form-group + %legend.col-form-label.col-form-label + = s_("Profiles|Achievements") + = f.gitlab_ui_checkbox_component :achievements_enabled, + s_('Profiles|Display achievements on your profile') + .row.js-hide-when-nothing-matches-search + .col-lg-12 + %hr + = f.submit s_("Profiles|Update profile settings"), class: 'gl-mr-3 js-password-prompt-btn', pajamas_button: true + = render Pajamas::ButtonComponent.new(href: user_path(current_user)) do + = s_('TagsPage|Cancel') -#password-prompt-modal + #password-prompt-modal -.modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } } - .modal-dialog - .modal-content - .modal-header - %h4.modal-title - = s_("Profiles|Position and size your new avatar") - = render Pajamas::ButtonComponent.new(category: :tertiary, - icon: 'close', - button_options: { class: 'close', "data-dismiss": "modal", "aria-label" => _("Close") }) - .modal-body - .profile-crop-image-container - %img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") } - .gl-text-center.gl-mt-4 - .btn-group - = render Pajamas::ButtonComponent.new(icon: 'search-minus', - button_options: {data: { method: 'zoom', option: '-0.1' }}) - = render Pajamas::ButtonComponent.new(icon: 'search-plus', - button_options: {data: { method: 'zoom', option: '0.1' }}) - .modal-footer - = render Pajamas::ButtonComponent.new(variant: :confirm, - button_options: { class: 'js-upload-user-avatar'}) do - = s_("Profiles|Set new profile picture") + .modal.modal-profile-crop{ data: { cropper_css_path: ActionController::Base.helpers.stylesheet_path('lazy_bundles/cropper.css') } } + .modal-dialog + .modal-content + .modal-header + %h4.modal-title + = s_("Profiles|Position and size your new avatar") + = render Pajamas::ButtonComponent.new(category: :tertiary, + icon: 'close', + button_options: { class: 'close', "data-dismiss": "modal", "aria-label" => _("Close") }) + .modal-body + .profile-crop-image-container + %img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") } + .gl-text-center.gl-mt-4 + .btn-group + = render Pajamas::ButtonComponent.new(icon: 'search-minus', + button_options: {data: { method: 'zoom', option: '-0.1' }}) + = render Pajamas::ButtonComponent.new(icon: 'search-plus', + button_options: {data: { method: 'zoom', option: '0.1' }}) + .modal-footer + = render Pajamas::ButtonComponent.new(variant: :confirm, + button_options: { class: 'js-upload-user-avatar'}) do + = s_("Profiles|Set new profile picture") diff --git a/config/feature_flags/development/user_profile_vue.yml b/config/feature_flags/development/user_profile_vue.yml new file mode 100644 index 00000000000000..492d109ac1ee7e --- /dev/null +++ b/config/feature_flags/development/user_profile_vue.yml @@ -0,0 +1,8 @@ +--- +name: user_profile_vue +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122402 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389918 +milestone: '16.1' +type: development +group: group::tenant scale +default_enabled: false -- GitLab From 22c215c0dc8e6a137f0c88f54299d00a8979478a Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Mon, 5 Jun 2023 11:33:48 +0800 Subject: [PATCH 2/7] Test: stub the newly added FF for testing --- db/structure.sql | 4 ++-- spec/features/admin/admin_appearance_spec.rb | 4 ++++ spec/features/file_uploads/user_avatar_spec.rb | 1 + spec/features/profiles/user_edit_profile_spec.rb | 1 + spec/features/profiles/user_search_settings_spec.rb | 1 + spec/features/profiles/user_visits_profile_spec.rb | 1 + spec/features/uploads/user_uploads_avatar_to_profile_spec.rb | 1 + spec/views/profiles/show.html.haml_spec.rb | 1 + 8 files changed, 12 insertions(+), 2 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index 8fe5a4c001103d..3cdb5b3e7d9301 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11684,7 +11684,7 @@ CREATE TABLE application_settings ( container_registry_import_start_max_retries integer DEFAULT 50 NOT NULL, container_registry_import_max_step_duration integer DEFAULT 300 NOT NULL, container_registry_import_target_plan text DEFAULT 'free'::text NOT NULL, - container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 00:00:00+00'::timestamp with time zone NOT NULL, + container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 08:00:00+08'::timestamp with time zone NOT NULL, runner_token_expiration_interval integer, group_runner_token_expiration_interval integer, project_runner_token_expiration_interval integer, @@ -31137,7 +31137,7 @@ CREATE INDEX index_events_on_author_id_and_created_at_merge_requests ON events U CREATE INDEX index_events_on_author_id_and_id ON events USING btree (author_id, id); -CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 00:00:00+00'::timestamp with time zone); +CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 08:00:00+08'::timestamp with time zone); CREATE INDEX index_events_on_group_id_and_id ON events USING btree (group_id, id) WHERE (group_id IS NOT NULL); diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index db0ae79c9c4854..ee77931a982f26 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -6,6 +6,10 @@ let!(:appearance) { create(:appearance) } let(:admin) { create(:admin) } + before do + stub_feature_flags(user_profile_vue: false) + end + flag_values = [true, false] flag_values.each do |val| context "with #{val}" do diff --git a/spec/features/file_uploads/user_avatar_spec.rb b/spec/features/file_uploads/user_avatar_spec.rb index 062c47d5310469..82be9d9341e70e 100644 --- a/spec/features/file_uploads/user_avatar_spec.rb +++ b/spec/features/file_uploads/user_avatar_spec.rb @@ -11,6 +11,7 @@ sign_in(user) visit(profile_path) attach_file('user_avatar-trigger', file.path, make_visible: true) + stub_feature_flags(user_profile_vue: false) click_button 'Set new profile picture' end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 3ce1c3a33a01a7..c22c33e352367d 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -10,6 +10,7 @@ before do sign_in(user) visit(profile_path) + stub_feature_flags(user_profile_vue: false) end def submit_settings diff --git a/spec/features/profiles/user_search_settings_spec.rb b/spec/features/profiles/user_search_settings_spec.rb index 932ea11075abd9..07059c8a1d8e22 100644 --- a/spec/features/profiles/user_search_settings_spec.rb +++ b/spec/features/profiles/user_search_settings_spec.rb @@ -7,6 +7,7 @@ before do sign_in(user) + stub_feature_flags(user_profile_vue: false) end context 'in profile page' do diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb index ad265fbae9eab7..6648e85548b910 100644 --- a/spec/features/profiles/user_visits_profile_spec.rb +++ b/spec/features/profiles/user_visits_profile_spec.rb @@ -7,6 +7,7 @@ before do stub_feature_flags(profile_tabs_vue: false) + stub_feature_flags(user_profile_vue: false) sign_in(user) end diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index f1023f17d3ea1a..c20eb72c80cc01 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -9,6 +9,7 @@ before do sign_in user visit profile_path + stub_feature_flags(user_profile_vue: false) end it 'they see their new avatar on their profile' do diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb index d5cb569403157f..1d29cd84e61edd 100644 --- a/spec/views/profiles/show.html.haml_spec.rb +++ b/spec/views/profiles/show.html.haml_spec.rb @@ -10,6 +10,7 @@ assign(:user, user) allow(controller).to receive(:current_user).and_return(user) allow(view).to receive(:experiment_enabled?) + stub_feature_flags(user_profile_vue: false) end context 'when the profile page is opened' do -- GitLab From 247970a315acc1d689fecc762b5059a68c49bec5 Mon Sep 17 00:00:00 2001 From: Wu Jeremy Date: Mon, 5 Jun 2023 04:05:48 +0000 Subject: [PATCH 3/7] Revert changes in strcuture.sql --- db/structure.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index 3cdb5b3e7d9301..8fe5a4c001103d 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11684,7 +11684,7 @@ CREATE TABLE application_settings ( container_registry_import_start_max_retries integer DEFAULT 50 NOT NULL, container_registry_import_max_step_duration integer DEFAULT 300 NOT NULL, container_registry_import_target_plan text DEFAULT 'free'::text NOT NULL, - container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 08:00:00+08'::timestamp with time zone NOT NULL, + container_registry_import_created_before timestamp with time zone DEFAULT '2022-01-23 00:00:00+00'::timestamp with time zone NOT NULL, runner_token_expiration_interval integer, group_runner_token_expiration_interval integer, project_runner_token_expiration_interval integer, @@ -31137,7 +31137,7 @@ CREATE INDEX index_events_on_author_id_and_created_at_merge_requests ON events U CREATE INDEX index_events_on_author_id_and_id ON events USING btree (author_id, id); -CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 08:00:00+08'::timestamp with time zone); +CREATE INDEX index_events_on_created_at_and_id ON events USING btree (created_at, id) WHERE (created_at > '2021-08-27 00:00:00+00'::timestamp with time zone); CREATE INDEX index_events_on_group_id_and_id ON events USING btree (group_id, id) WHERE (group_id IS NOT NULL); -- GitLab From e7f4ef958c0e576b93ef6284400cff1521381e33 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Mon, 5 Jun 2023 14:17:25 +0800 Subject: [PATCH 4/7] Move FF stub to the start of the before action --- spec/features/file_uploads/user_avatar_spec.rb | 2 +- spec/features/profiles/user_edit_profile_spec.rb | 2 +- spec/features/uploads/user_uploads_avatar_to_profile_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/file_uploads/user_avatar_spec.rb b/spec/features/file_uploads/user_avatar_spec.rb index 82be9d9341e70e..7fb198020ac448 100644 --- a/spec/features/file_uploads/user_avatar_spec.rb +++ b/spec/features/file_uploads/user_avatar_spec.rb @@ -8,10 +8,10 @@ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } before do + stub_feature_flags(user_profile_vue: false) sign_in(user) visit(profile_path) attach_file('user_avatar-trigger', file.path, make_visible: true) - stub_feature_flags(user_profile_vue: false) click_button 'Set new profile picture' end diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index c22c33e352367d..0229e3e032ed4b 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -8,9 +8,9 @@ let_it_be(:user) { create(:user) } before do + stub_feature_flags(user_profile_vue: false) sign_in(user) visit(profile_path) - stub_feature_flags(user_profile_vue: false) end def submit_settings diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index c20eb72c80cc01..61097652be2c14 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -7,9 +7,9 @@ let(:avatar_file_path) { Rails.root.join('spec', 'fixtures', 'dk.png') } before do + stub_feature_flags(user_profile_vue: false) sign_in user visit profile_path - stub_feature_flags(user_profile_vue: false) end it 'they see their new avatar on their profile' do -- GitLab From dd0bfacc7ed6a60a1653dae7c9d4648ecb295368 Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Mon, 5 Jun 2023 16:25:51 +0800 Subject: [PATCH 5/7] Feat: add an empty Vue component for mounting --- .../profile/edit/components/profile_edit_app.vue | 7 +++++++ app/assets/javascripts/profile/edit/index.js | 16 ++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 app/assets/javascripts/profile/edit/components/profile_edit_app.vue create mode 100644 app/assets/javascripts/profile/edit/index.js diff --git a/app/assets/javascripts/profile/edit/components/profile_edit_app.vue b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue new file mode 100644 index 00000000000000..6d32ba41eaeeab --- /dev/null +++ b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue @@ -0,0 +1,7 @@ + + + diff --git a/app/assets/javascripts/profile/edit/index.js b/app/assets/javascripts/profile/edit/index.js new file mode 100644 index 00000000000000..b46a395d6f5bdc --- /dev/null +++ b/app/assets/javascripts/profile/edit/index.js @@ -0,0 +1,16 @@ +import Vue from 'vue'; +import ProfileEditApp from './components/profile_edit_app.vue'; + +export const initProfileEdit = () => { + const mountEl = document.querySelector('.js-user-profile'); + + if (!mountEl) return false; + + return new Vue({ + el: mountEl, + name: 'ProfileEditRoot', + render(createElement) { + return createElement(ProfileEditApp); + }, + }); +}; -- GitLab From 56076e86ac5be015f519a1217e4cb3064159ee8d Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Wed, 7 Jun 2023 09:35:13 +0800 Subject: [PATCH 6/7] Rename feature flag and address MR comments --- app/assets/javascripts/pages/profiles/show/index.js | 3 +++ .../javascripts/profile/edit/components/profile_edit_app.vue | 3 +++ app/views/profiles/show.html.haml | 2 +- .../{user_profile_vue.yml => edit_user_profile_vue.yml} | 4 ++-- spec/features/admin/admin_appearance_spec.rb | 2 +- spec/features/file_uploads/user_avatar_spec.rb | 2 +- spec/features/profiles/user_edit_profile_spec.rb | 2 +- spec/features/profiles/user_search_settings_spec.rb | 2 +- spec/features/profiles/user_visits_profile_spec.rb | 2 +- spec/features/uploads/user_uploads_avatar_to_profile_spec.rb | 2 +- spec/views/profiles/show.html.haml_spec.rb | 2 +- 11 files changed, 16 insertions(+), 10 deletions(-) rename config/feature_flags/development/{user_profile_vue.yml => edit_user_profile_vue.yml} (86%) diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index 96ea7329e6e1e1..69457adf94e9fb 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -1,8 +1,11 @@ import emojiRegex from 'emoji-regex'; import { __ } from '~/locale'; import { initSetStatusForm } from '~/profile/profile'; +import { initProfileEdit } from '~/profile/edit'; initSetStatusForm(); +// It will do nothing for now when the feature flag is turned off +initProfileEdit(); const userNameInput = document.getElementById('user_name'); if (userNameInput) { diff --git a/app/assets/javascripts/profile/edit/components/profile_edit_app.vue b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue index 6d32ba41eaeeab..ab29d94c41c79b 100644 --- a/app/assets/javascripts/profile/edit/components/profile_edit_app.vue +++ b/app/assets/javascripts/profile/edit/components/profile_edit_app.vue @@ -3,5 +3,8 @@ export default {}; diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index be2a4e07189420..1c440a3c1c000d 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -4,7 +4,7 @@ - gravatar_link = link_to Gitlab.config.gravatar.host, 'https://' + Gitlab.config.gravatar.host - @force_desktop_expanded_sidebar = true -- if Feature.enabled?(:user_profile_vue) +- if Feature.enabled?(:edit_user_profile_vue, current_user) .js-user-profile - else = gitlab_ui_form_for @user, url: profile_path, method: :put, html: { multipart: true, class: 'edit-user js-edit-user gl-mt-3 js-quick-submit gl-show-field-errors js-password-prompt-form', remote: true }, authenticity_token: true do |f| diff --git a/config/feature_flags/development/user_profile_vue.yml b/config/feature_flags/development/edit_user_profile_vue.yml similarity index 86% rename from config/feature_flags/development/user_profile_vue.yml rename to config/feature_flags/development/edit_user_profile_vue.yml index 492d109ac1ee7e..93a50457339e3d 100644 --- a/config/feature_flags/development/user_profile_vue.yml +++ b/config/feature_flags/development/edit_user_profile_vue.yml @@ -1,7 +1,7 @@ --- -name: user_profile_vue +name: edit_user_profile_vue introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/122402 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/389918 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/414552 milestone: '16.1' type: development group: group::tenant scale diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index ee77931a982f26..71c904b3a199c3 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -7,7 +7,7 @@ let(:admin) { create(:admin) } before do - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) end flag_values = [true, false] diff --git a/spec/features/file_uploads/user_avatar_spec.rb b/spec/features/file_uploads/user_avatar_spec.rb index 7fb198020ac448..3f7d69afa0b6f4 100644 --- a/spec/features/file_uploads/user_avatar_spec.rb +++ b/spec/features/file_uploads/user_avatar_spec.rb @@ -8,7 +8,7 @@ let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } before do - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) sign_in(user) visit(profile_path) attach_file('user_avatar-trigger', file.path, make_visible: true) diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 0229e3e032ed4b..de8719630eebbb 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -8,7 +8,7 @@ let_it_be(:user) { create(:user) } before do - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) sign_in(user) visit(profile_path) end diff --git a/spec/features/profiles/user_search_settings_spec.rb b/spec/features/profiles/user_search_settings_spec.rb index 07059c8a1d8e22..96fe01cd0c2195 100644 --- a/spec/features/profiles/user_search_settings_spec.rb +++ b/spec/features/profiles/user_search_settings_spec.rb @@ -7,7 +7,7 @@ before do sign_in(user) - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) end context 'in profile page' do diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb index 6648e85548b910..578025e1494cab 100644 --- a/spec/features/profiles/user_visits_profile_spec.rb +++ b/spec/features/profiles/user_visits_profile_spec.rb @@ -7,7 +7,7 @@ before do stub_feature_flags(profile_tabs_vue: false) - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) sign_in(user) end diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 61097652be2c14..03b072ea417805 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -7,7 +7,7 @@ let(:avatar_file_path) { Rails.root.join('spec', 'fixtures', 'dk.png') } before do - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) sign_in user visit profile_path end diff --git a/spec/views/profiles/show.html.haml_spec.rb b/spec/views/profiles/show.html.haml_spec.rb index 1d29cd84e61edd..ea0a9ebb02cde6 100644 --- a/spec/views/profiles/show.html.haml_spec.rb +++ b/spec/views/profiles/show.html.haml_spec.rb @@ -10,7 +10,7 @@ assign(:user, user) allow(controller).to receive(:current_user).and_return(user) allow(view).to receive(:experiment_enabled?) - stub_feature_flags(user_profile_vue: false) + stub_feature_flags(edit_user_profile_vue: false) end context 'when the profile page is opened' do -- GitLab From fe36c432e9cd0cfa7a8d7fe03fc2fcb87d282dea Mon Sep 17 00:00:00 2001 From: JeremyWuuuuu Date: Fri, 9 Jun 2023 17:50:35 +0800 Subject: [PATCH 7/7] Chore: use safe_format instead of html_safe --- app/views/profiles/show.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 1c440a3c1c000d..1a932ed7b35538 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -110,8 +110,8 @@ = f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username") .form-group.gl-form-group - external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page') - - external_accounts_link_start = ''.html_safe % { url: external_accounts_help_url } - - external_accounts_docs_link = s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}').html_safe % { external_accounts_link_start: external_accounts_link_start, external_accounts_link_end: ''.html_safe } + - external_accounts_link = link_to '', external_accounts_help_url, target: "_blank", rel: "noopener noreferrer" + - external_accounts_docs_link = safe_format(s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more.%{external_accounts_link_end}'), tag_pair(external_accounts_link, :external_accounts_link_start, :external_accounts_link_end)) - min_discord_length = 17 - max_discord_length = 20 = f.label :discord -- GitLab