From 9377eee4f52e5fee65c6ec9df5e71fbceba4849c Mon Sep 17 00:00:00 2001 From: Michael Kohl Date: Tue, 20 Oct 2020 20:18:13 +0700 Subject: [PATCH] [deploy] /profile/settings page rendering (Ridhwana/Michael) (#10733) * chore: update the profile to be more dynamic. Maps out the group + field name + value for field * feat: add the correct input type to the form tying it to the correct method to update * feat: update the value and the label * feat: update the profile with a submit * refactor: ensure we dont show empty groups on the settings/profile * fix: this shoudl be dribbble * fix: use attributes! to refresh the cache to ensure that we font run into the case where Profile.attributes is [] * feat: show group description only when it exists * chore: add some classes * feat: use the profile update service to update the relevant user information * chore: rename variable * chore: use a scope to not return empty groups * fix: only update user if the profile sync is update is successful. Also capture errors * feat: style the crayons field checkbox * refactor: rename the attribute to user instead of profile * refactor: service object * Fix CodeClimate warning and rearrange code * Fix service object, add missing attribute * Fix flash and add redirect * Fix service object + spec * Fix user edits profile spec * Disable spec * Fix more specs * Undo vim fail * Add data update scripts * Update where clause in data update script * Rename method * Make sync_to_user conditional * Add after update actions and Honeycomb * feat: handle checkbox allow chcekbox to check * Don't set experience cookie in ProfilesController * Clean up Profiles::Update object * Fix spec * Remove Twitch integration (#10732) * Remove Twitch integration * Ignore columns * Change admin password in docs (#10735) Update default admin password in documentation * Do not require meta_keywords to be set (#10721) * Remove extra character from meta_keywords in /listings/index.html.erb * Remove meta_keywords from MANDATORY_CONFIGS * Add and use meta_keywords_tag helper * Use modern tag syntax instead of deprecated syntax * Add and use meta_keywords_default helper * Add and use meta_keywords_article helper * Remove * from meta_keywords_field.label * Update meta_keyword specs to account for no keywords being set * Generalizes Connect welcome message and settings nav tab and refactors tests (#10741) [deploy] * [deploy] fix: Clear chat input after Enter key submit (#10487) * fix: Clear chat input after Enter key submit * fix: Rename Composer component * test: Add integration tests for input value * After pressing Enter * After clicking Send * After clicking Save edit * After clicking Close edit * Remove tabindex of -1 on upload image input in the write a post editor (#10543) * Remove tabindex of -1 on upload image input in the write a post editor * Remove negative tabindex on image uploader's outer button * Give tabindex of -1 to outer button on image uploader * Update buttons.scss * [deploy] Add recaptcha keys to as site-configurable keys (#10736) * Add recaptcha keys to as site-configurable keys * Add recaptcha to site config view * Use site config key * Make ReCAPTCHA tag optional if keys are blank * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Refactor logic for readability * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Use proper capitalization of recaptcha Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> * Add Section for Series/Collections (#10719) * Add Section for Series/Collections I added a small section in this area of the docs to share some information about collections/ series. There is not much information in the documentation about collections or series and I felt like this would be beneficial to others looking to help with the Series feature of the website. * Updated wording of collections section. * Articles API: filtering by tags (#3654) (#10614) * Articles API: filtering by tags (#3654) * Address CR comments (#3654) * Add test case * Move tests * Update docs * Use _any_ option for querying by tags (#3654) * Move Imgproxy endpoint config to Images::Optimizer (#10742) * Link Email Account creation UI to existing Create Account UI (#10746) [deploy] * Conditional button to link to email accout creation * Rename email signup state param * Test for email account creation button scenarios * Add forem meta tags (#10747) * Add forem meta tags * Add docs * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington * Update docs/technical-overview/compatibility.md Co-authored-by: Jacob Herrington * Remove Twitch-related columns from users table (#10750) * [deploy] Long text goes outside cards (#10740) * Add word-break to crayons-card Add word-break to crayons-card this will allow words to be broken if the word is too long but will wrap normally otherwise. * Use overflow-wrap: anywhere; `break-word` is deprecated though `anywhere` does not work in IE * Removes superfluous sign_in from users_onboarding_spec.rb (#10757) [deploy] * [deploy] Allow glitch id starting with ~ (#10544) * [deploy] crayons prep (#10737) * Add DEV special case for following hiring tag * Move image validation to Profiles::Update object * spec: move over the tests for validating images * Move follow_hiring_tag into the service object Co-authored-by: Ridhwana Co-authored-by: Andrew Bone Co-authored-by: Vaidehi Joshi Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com> Co-authored-by: Robin Gagnon Co-authored-by: Manaswini <58681405+Manaswini1832@users.noreply.github.com> Co-authored-by: Andy Zhao <17884966+Zhao-Andy@users.noreply.github.com> Co-authored-by: Christopher Wray <53663762+cwray-tech@users.noreply.github.com> Co-authored-by: Rafal Trojanowski Co-authored-by: Mac Siri Co-authored-by: Josh Puetz Co-authored-by: Ben Halpern Co-authored-by: Jacob Herrington Co-authored-by: irmela Co-authored-by: ludwiczakpawel --- app/controllers/profiles_controller.rb | 11 +- app/controllers/users_controller.rb | 41 ---- app/models/profile.rb | 1 + app/models/profile_field_group.rb | 4 + .../profile_fields/add_link_fields.rb | 2 +- app/services/profiles/update.rb | 113 ++++++++--- app/views/users/_profile.html.erb | 188 +++--------------- lib/data/dev_profile_fields.csv | 2 +- ...22065307_fix_dribbble_url_profile_field.rb | 18 ++ ...72907_backfill_profile_skills_languages.rb | 9 + spec/requests/user/user_settings_spec.rb | 22 -- spec/services/profiles/update_spec.rb | 49 ++++- spec/system/user/user_edits_profile_spec.rb | 4 +- .../user_selects_looking_for_work_spec.rb | 19 +- 14 files changed, 214 insertions(+), 269 deletions(-) create mode 100644 lib/data_update_scripts/20200922065307_fix_dribbble_url_profile_field.rb create mode 100644 lib/data_update_scripts/20200922072907_backfill_profile_skills_languages.rb diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index 41bd289d2..453781251 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -1,13 +1,20 @@ class ProfilesController < ApplicationController before_action :authenticate_user! + ALLOWED_USER_PARAMS = %i[email username profile_image].freeze def update - Profiles::Update.call(current_user.profile, update_params) + update_result = Profiles::Update.call(current_user, update_params) + if update_result.success? + flash[:settings_notice] = "Your profile has been updated" + else + flash[:error] = "Error: #{update_result.error_message}" + end + redirect_to user_settings_path end private def update_params - params.require(:profile).permit(*Profile.attributes) + params.permit(profile: Profile.attributes!, user: ALLOWED_USER_PARAMS) end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index cc0b8b581..14265ae6c 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -39,11 +39,6 @@ class UsersController < ApplicationController def update set_current_tab(params["user"]["tab"]) - unless valid_image? - render :edit, status: :bad_request - return - end - if @user.update(permitted_attributes(@user)) RssReaderFetchUserWorker.perform_async(@user.id) if @user.feed_url.present? notice = "Your profile was successfully updated." @@ -55,7 +50,6 @@ class UsersController < ApplicationController ExportContentWorker.perform_async(@user.id) end cookies.permanent[:user_experience_level] = @user.experience_level.to_s if @user.experience_level.present? - follow_hiring_tag(@user) flash[:settings_notice] = notice @user.touch(:profile_updated_at) redirect_to "/settings/#{@tab}" @@ -234,15 +228,6 @@ class UsersController < ApplicationController def signout_confirm; end - def follow_hiring_tag(user) - return unless user.looking_for_work? - - hiring_tag = Tag.find_by(name: "hiring") - return if !hiring_tag || user.following?(hiring_tag) - - Users::FollowWorker.perform_async(user.id, hiring_tag.id, "Tag") - end - def handle_settings_tab return @tab = "profile" if @tab.blank? @@ -355,32 +340,6 @@ class UsersController < ApplicationController range.cover?(user_identity_age) end - def valid_image? - image = params.dig("user", "profile_image") - return true unless image - - return true if valid_image_file?(image) && valid_filename?(image) - - Honeycomb.add_field("error", @user.errors.messages) - Honeycomb.add_field("errored", true) - - false - end - - def valid_image_file?(image) - return true if file?(image) - - @user.errors.add(:profile_image, IS_NOT_FILE_MESSAGE) - false - end - - def valid_filename?(image) - return true unless long_filename?(image) - - @user.errors.add(:profile_image, FILENAME_TOO_LONG_MESSAGE) - false - end - def destroy_request_in_progress? Rails.cache.exist?("user-destroy-token-#{@user.id}") end diff --git a/app/models/profile.rb b/app/models/profile.rb index a881c9c5f..60ef7c8a8 100644 --- a/app/models/profile.rb +++ b/app/models/profile.rb @@ -18,6 +18,7 @@ class Profile < ApplicationRecord git_lab_url: :gitlab_url, linked_in_url: :linkedin_url, recruiters_can_contact_me_about_job_opportunities: :contact_consent, + skills_languages: :mostly_work_with, stack_overflow_url: :stackoverflow_url }.with_indifferent_access.freeze diff --git a/app/models/profile_field_group.rb b/app/models/profile_field_group.rb index c0e02afc5..e54567230 100644 --- a/app/models/profile_field_group.rb +++ b/app/models/profile_field_group.rb @@ -6,4 +6,8 @@ class ProfileFieldGroup < ApplicationRecord scope :onboarding, lambda { includes(:profile_fields).where(profile_fields: { show_in_onboarding: true }) } + + scope :non_empty_groups, lambda { + joins(:profile_fields).includes(:profile_fields).order(:name).uniq + } end diff --git a/app/services/profile_fields/add_link_fields.rb b/app/services/profile_fields/add_link_fields.rb index b290c3c81..75fb0708f 100644 --- a/app/services/profile_fields/add_link_fields.rb +++ b/app/services/profile_fields/add_link_fields.rb @@ -8,7 +8,7 @@ module ProfileFields field "StackOverflow URL", :text_field, placeholder: "https://stackoverflow.com/users/..." field "LinkedIn URL", :text_field, placeholder: "https://www.linkedin.com/in/..." field "Behance URL", :text_field, placeholder: "https://www.behance.net/..." - field "Dribble URL", :text_field, placeholder: "https://dribble.com/..." + field "Dribbble URL", :text_field, placeholder: "https://dribbble.com/..." field "Medium URL", :text_field, placeholder: "https://medium.com/@..." field "GitLab URL", :text_field, placeholder: "https://gitlab.com/..." field "Instagram URL", :text_field, placeholder: "https://www.instagram.com/..." diff --git a/app/services/profiles/update.rb b/app/services/profiles/update.rb index 7e1a21757..65a30d989 100644 --- a/app/services/profiles/update.rb +++ b/app/services/profiles/update.rb @@ -1,36 +1,29 @@ module Profiles class Update - def self.call(profile, updated_attributes = {}) - new(profile, updated_attributes).call + include ImageUploads + + def self.call(user, updated_attributes = {}) + new(user, updated_attributes).call end - attr_reader + attr_reader :error_message - def initialize(profile, updated_attributes) - @profile = profile - @updated_attributes = updated_attributes + def initialize(user, updated_attributes) + @user = user + @profile = user.profile + @updated_profile_attributes = updated_attributes[:profile] || {} + @updated_user_attributes = updated_attributes[:user].to_h || {} @success = false end def call - # Ensure we have up to date attributes - Profile.refresh_attributes! - - # Handle user specific custom profile fields - custom_attributes = @updated_attributes.extract!(*@profile.custom_profile_attributes) - @updated_attributes[:custom_attributes] = custom_attributes - - # We don't update `data` directly. This uses the defined store_attributes - # so we can make use of their typecasting. - @profile.assign_attributes(@updated_attributes) - - # Before saving, filter out obsolete profile fields - @profile.data.slice!(*Profile.attributes) - - return unless @profile.save - - # Propagate changes back to the `users` table - sync_to_user + if verify_profile_image && update_profile && sync_to_user + @user.touch(:profile_updated_at) + follow_hiring_tag + else + Honeycomb.add_field("error", @error_message) + Honeycomb.add_field("errored", true) + end self end @@ -40,13 +33,81 @@ module Profiles private + def verify_profile_image + image = @updated_user_attributes[:profile_image] + return true unless image + return true if valid_image_file?(image) && valid_filename?(image) + + false + end + + def valid_image_file?(image) + return true if file?(image) + + @error_message = IS_NOT_FILE_MESSAGE + false + end + + def valid_filename?(image) + return true unless long_filename?(image) + + @error_message = FILENAME_TOO_LONG_MESSAGE + false + end + + def update_profile + # Ensure we have up to date attributes + Profile.refresh_attributes! + + # Handle user specific custom profile fields + if (custom_profile_attributes = @profile.custom_profile_attributes).any? + custom_attributes = @updated_profile_attributes.extract!(*custom_profile_attributes) + @updated_profile_attributes[:custom_attributes] = custom_attributes + end + + # We don't update `data` directly. This uses the defined store_attributes + # so we can make use of their typecasting. + @profile.assign_attributes(@updated_profile_attributes) + + # Before saving, filter out obsolete profile fields + @profile.data.slice!(*Profile.attributes) + + @profile.save + end + + # Propagate changes back to the `users` table def sync_to_user - user_attributes = @profile.data.transform_keys do |key| + # These are the profile attributes that still exist as columns on User. + profile_attributes = @profile.data.transform_keys do |key| Profile::MAPPED_ATTRIBUTES.fetch(key, key).to_s end @profile.user._skip_profile_sync = true - @success = true if @profile.user.update(user_attributes.except("custom_attributes")) + if @profile.user.update(profile_attributes.except("custom_attributes")) + update_user_attributes + else + @error_message = @user.errors_as_sentence + end + + @success + ensure @profile.user._skip_profile_sync = false end + + def update_user_attributes + if @user.update(@updated_user_attributes) + @success = true + else + @error_message = @user.errors_as_sentence + end + end + + def follow_hiring_tag + return unless SiteConfig.dev_to? && @user.looking_for_work? + + hiring_tag = Tag.find_by(name: "hiring") + return unless hiring_tag && @user.following?(hiring_tag) + + Users::FollowWorker.perform_async(@user.id, hiring_tag.id, "Tag") + end end end diff --git a/app/views/users/_profile.html.erb b/app/views/users/_profile.html.erb index 374fe94ff..4cfe2c1f3 100644 --- a/app/views/users/_profile.html.erb +++ b/app/views/users/_profile.html.erb @@ -15,26 +15,18 @@ <% end %> -<%= form_for @user, html: { class: "sticky-footer-form" } do |f| %> +<%= form_with(url: profile_path(@user.profile_id), method: "put", html: { class: "sticky-footer-form" }) do |f| %> +
+

User

<%= f.label :email, class: "crayons-field__label" %> - <%= f.text_field :email, class: "crayons-textfield", placeholder: "john.doe@example.com" %> -
- -
- <%= f.check_box :email_public, class: "crayons-checkbox" %> - <%= f.label :email_public, "Display email on profile", class: "crayons-field__label" %> + <%= f.text_field "user[email]", class: "crayons-textfield", placeholder: "john.doe@example.com", value: @user.email %>
<%= f.label :username, class: "crayons-field__label" %> - <%= f.text_field :username, maxlength: 30, class: "crayons-textfield", placeholder: "johndoe" %> -
- -
- <%= f.label :name, class: "crayons-field__label" %> - <%= f.text_field :name, maxlength: 100, class: "crayons-textfield", placeholder: "John Doe" %> + <%= f.text_field "user[username]", maxlength: 30, class: "crayons-textfield", placeholder: "johndoe", value: @user.username %>
@@ -43,158 +35,32 @@
<% if @user.profile_image_url.present? %> <%= @user.username %> profile image - <%= f.file_field :profile_image, accept: "image/*", class: "crayons-card crayons-card--secondary p-3 flex items-center flex-1 w-100" %> + <%= f.file_field "user[profile_image]", accept: "image/*", class: "crayons-card crayons-card--secondary p-3 flex items-center flex-1 w-100" %> <% end %>
- -
- <%= f.label :website_url, class: "crayons-field__label" %> - <%= f.text_field :website_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://yoursite.com" %> -
- -
- <%= f.label :summary, class: "crayons-field__label" %> - <%= f.text_area :summary, maxlength: 200, class: "crayons-textfield", placeholder: "A short bio...", rows: 3 %> -
- -
- <%= f.label :location, class: "crayons-field__label" %> - <%= f.text_field :location, maxlength: 100, class: "crayons-textfield", placeholder: "Halifax, Nova Scotia" %> -
- -
- <%= f.label :education, class: "crayons-field__label" %> - <%= f.text_field :education, maxlength: 100, class: "crayons-textfield" %> -
- -
- <%= f.label :employer_name, class: "crayons-field__label" %> - <%= f.text_field :employer_name, maxlength: 100, class: "crayons-textfield", placeholder: "Acme Inc." %> -
- -
- <%= f.label :employer_url, class: "crayons-field__label" %> - <%= f.text_field :employer_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://dev.com" %> -
- -
- <%= f.label :employment_title, class: "crayons-field__label" %> - <%= f.text_field :employment_title, maxlength: 100, class: "crayons-textfield", placeholder: "Junior Frontend Engineer" %> -
- -
- <%= f.label :mostly_work_with, "Skills/Languages", class: "crayons-field__label" %> - <%= f.text_area :mostly_work_with, maxlength: 500, class: "crayons-textfield", rows: 5 %> -

What tools and languages are you most experienced with? Are you specialized or more of a generalist?

-
- -
- <%= f.label :currently_learning, "I'm getting into", class: "crayons-field__label" %> - <%= f.text_area :currently_learning, maxlength: 500, class: "crayons-textfield", rows: 5 %> -

What are you learning right now? What are the new tools and languages you're picking up in <%= Time.zone.now.year %>?

-
- -
- <%= f.label :currently_hacking_on, "My projects and hacks", class: "crayons-field__label" %> - <%= f.text_area :currently_hacking_on, maxlength: 500, class: "crayons-textfield", rows: 5 %> -

What projects are currently occupying most of your time?

-
- -
- <%= f.label :available_for, class: "crayons-field__label" %> - <%= f.text_area :available_for, maxlength: 500, class: "crayons-textfield", rows: 5 %> -

What kinds of collaborations or discussions are you available for? What's a good reason to say Hey! to you these days?

-
-
-

Branding

- <%= render partial: "shared/logo_design", locals: { f: f, account: @user } %> + <% ProfileFieldGroup.non_empty_groups.each do |group| %> +
+

<%= group.name %>

+ <% if group.description.present? %> +
<%= group.description %>
+ <% end %> + <% group.profile_fields.each do |field| %> +
"> +
<%= field.label %>
+ <% if field["input_type"] == "check_box" %> + <%= f.public_send(field["input_type"], "profile[#{field.attribute_name}]", checked: @user.profile.data[field.attribute_name]) %> + <% else %> + <%= f.public_send(field["input_type"], "profile[#{field.attribute_name}]", value: @user.profile.data[field.attribute_name]) %> + <% end %> +
+ <% end %> +
+ <% end %> + + - -
-

Work opportunities settings

-
- <%= f.check_box :looking_for_work, class: "crayons-checkbox" %> - <%= f.label :looking_for_work, class: "crayons-field__label" %> -
- -
- <%= f.check_box :looking_for_work_publicly, class: "crayons-checkbox" %> - <%= f.label :looking_for_work_publicly, "Display \"looking for work\" on profile", class: "crayons-field__label" %> -
- -
- <%= f.check_box :contact_consent, class: "crayons-checkbox" %> - <%= f.label :contact_consent, "Recruiters can contact me about job opportunities", class: "crayons-field__label" %> -
-
- -
-

Links

- -
- <%= f.label :facebook_url, "Facebook profile URL", class: "crayons-field__label" %> - <%= f.url_field :facebook_url, class: "crayons-textfield", placeholder: "https://facebook.com/..." %> -
- -
- <%= f.label :youtube_url, "Youtube URL", class: "crayons-field__label" %> - <%= f.url_field :youtube_url, class: "crayons-textfield", placeholder: "https://www.youtube.com/channel/..." %> -
- -
- <%= f.label :stackoverflow_url, "Stack Overflow profile URL", class: "crayons-field__label" %> - <%= f.url_field :stackoverflow_url, class: "crayons-textfield", placeholder: "https://stackoverflow.com/users/..." %> -
- -
- <%= f.label :linkedin_url, "LinkedIn profile URL", class: "crayons-field__label" %> - <%= f.url_field :linkedin_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :behance_url, "Behance profile URL", class: "crayons-field__label" %> - <%= f.url_field :behance_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :dribbble_url, "Dribbble profile URL", class: "crayons-field__label" %> - <%= f.url_field :dribbble_url, class: "crayons-textfield", placeholder: "https://dribbble.com/..." %> -
- -
- <%= f.label :medium_url, "Medium profile URL", class: "crayons-field__label" %> - <%= f.url_field :medium_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :gitlab_url, "GitLab profile URL", class: "crayons-field__label" %> - <%= f.url_field :gitlab_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :instagram_url, "Instagram profile URL", class: "crayons-field__label" %> - <%= f.url_field :instagram_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :mastodon_url, "Mastodon profile URL", class: "crayons-field__label" %> - <%= f.url_field :mastodon_url, class: "crayons-textfield", placeholder: "https://..." %> -
- -
- <%= f.label :twitch_url, "Twitch profile URL", class: "crayons-field__label" %> - <%= f.url_field :twitch_url, class: "crayons-textfield", placeholder: "https://..." %> -
-
- - - <% end %> diff --git a/lib/data/dev_profile_fields.csv b/lib/data/dev_profile_fields.csv index ed05e45d9..6962c6221 100644 --- a/lib/data/dev_profile_fields.csv +++ b/lib/data/dev_profile_fields.csv @@ -8,7 +8,7 @@ Youtube URL,text_field,https://www.youtube.com/channel/...,,Links StackOverflow URL,text_field,https://stackoverflow.com/users/...,,Links LinkedIn URL,text_field,https://www.linkedin.com/in/...,,Links Behance URL,text_field,https://www.behance.net/...,,Links -Dribble URL,text_field,https://dribble.com/...,,Links +Dribbble URL,text_field,https://dribbble.com/...,,Links Medium URL,text_field,https://medium.com/@...,,Links GitLab URL,text_field,https://gitlab.com/...,,Links Instagram URL,text_field,https://www.instagram.com/...,,Links diff --git a/lib/data_update_scripts/20200922065307_fix_dribbble_url_profile_field.rb b/lib/data_update_scripts/20200922065307_fix_dribbble_url_profile_field.rb new file mode 100644 index 000000000..557d211ae --- /dev/null +++ b/lib/data_update_scripts/20200922065307_fix_dribbble_url_profile_field.rb @@ -0,0 +1,18 @@ +module DataUpdateScripts + class FixDribbbleUrlProfileField + def run + old_field = ProfileField.find_by(attribute_name: "dribble_url") + return unless old_field + + old_field.update(label: "Dribbble URL", + attribute_name: "dribbble_url", + placeholder_text: "https://dribbble.com/...") + + Profile.refresh_attributes! + + User.where.not(dribbble_url: [nil, ""]).find_each do |user| + user.profile.update(dribbble_url: user.dribbble_url) + end + end + end +end diff --git a/lib/data_update_scripts/20200922072907_backfill_profile_skills_languages.rb b/lib/data_update_scripts/20200922072907_backfill_profile_skills_languages.rb new file mode 100644 index 000000000..ceb5aa634 --- /dev/null +++ b/lib/data_update_scripts/20200922072907_backfill_profile_skills_languages.rb @@ -0,0 +1,9 @@ +module DataUpdateScripts + class BackfillProfileSkillsLanguages + def run + User.where.not(mostly_work_with: [nil, ""]).find_each do |user| + user.profile.update(skills_languages: user.mostly_work_with) + end + end + end +end diff --git a/spec/requests/user/user_settings_spec.rb b/spec/requests/user/user_settings_spec.rb index 08bdaea65..d210d889a 100644 --- a/spec/requests/user/user_settings_spec.rb +++ b/spec/requests/user/user_settings_spec.rb @@ -199,28 +199,6 @@ RSpec.describe "UserSettings", type: :request do expect(response.body).to include("Username is too short") end - it "returns error if Profile image is too large" do - profile_image = fixture_file_upload("files/large_profile_img.jpg", "image/jpeg") - put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } } - expect(response.body).to include("Profile image File size should be less than 2 MB") - end - - it "returns error if Profile image file name is too long" do - profile_image = fixture_file_upload("files/800x600.png", "image/png") - allow(profile_image).to receive(:original_filename).and_return("#{'a_very_long_filename' * 15}.png") - - put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } } - - expect(response).to have_http_status(:bad_request) - end - - it "returns error if Profile image is not a file" do - profile_image = "A String" - put "/users/#{user.id}", params: { user: { tab: "profile", profile_image: profile_image } } - - expect(response).to have_http_status(:bad_request) - end - it "returns error message if user can't be saved" do put "/users/#{user.id}", params: { user: { password: "1", password_confirmation: "1" } } diff --git a/spec/services/profiles/update_spec.rb b/spec/services/profiles/update_spec.rb index e8d00df53..a5002920d 100644 --- a/spec/services/profiles/update_spec.rb +++ b/spec/services/profiles/update_spec.rb @@ -4,6 +4,7 @@ RSpec.describe Profiles::Update, type: :service do let(:profile) do create(:profile, data: { name: "Sloan Doe", looking_for_work: true, removed: "Bla" }) end + let(:user) { profile.user } before do create(:profile_field, label: "Name", input_type: :text_field) @@ -11,21 +12,29 @@ RSpec.describe Profiles::Update, type: :service do Profile.refresh_attributes! end + it "only tries to sync changes to User if the profile update succeeds" do + service = described_class.new(user, profile: {}, user: {}) + allow(service).to receive(:update_profile).and_return(false) + + expect(service).not_to receive(:sync_to_user) # rubocop:disable RSpec/MessageSpies + service.call + end + it "correctly typecasts new attributes", :aggregate_failures do - described_class.call(profile, name: 123, looking_for_work: "false") + described_class.call(user, profile: { name: 123, looking_for_work: "false" }) expect(profile.name).to eq "123" expect(profile.looking_for_work).to be false end it "removes old attributes from the profile" do expect do - described_class.call(profile, {}) + described_class.call(user, profile: {}) end.to change { profile.data.key?("removed") }.to(false) end it "propagates changes to user", :agregate_failures do new_name = "Sloan Doe" - described_class.call(profile, name: new_name) + described_class.call(user, profile: {}, user: { name: new_name }) expect(profile.name).to eq new_name expect(profile.user[:name]).to eq new_name end @@ -34,8 +43,38 @@ RSpec.describe Profiles::Update, type: :service do custom_profile_field = create(:custom_profile_field, profile: profile) custom_attribute = custom_profile_field.attribute_name - described_class.call(profile, custom_attribute => "Test") - + described_class.call(user, profile: { custom_attribute => "Test" }, user: {}) expect(profile.custom_attributes[custom_attribute]).to eq "Test" end + + it "updates the profile_updated_at column" do + expect do + described_class.call(user, profile: { name: 123, looking_for_work: "false" }) + end.to change { user.reload.profile_updated_at } + end + + it "returns an error if Profile image is too large" do + profile_image = fixture_file_upload("files/large_profile_img.jpg", "image/jpeg") + service = described_class.call(user, profile: {}, user: { profile_image: profile_image }) + + expect(service.success?).to be false + expect(service.error_message).to eq "Profile image File size should be less than 2 MB" + end + + it "returns an error if Profile image is not a file" do + profile_image = "A String" + service = described_class.call(user, profile: {}, user: { profile_image: profile_image }) + + expect(service.success?).to be false + expect(service.error_message).to eq "invalid file type. Please upload a valid image." + end + + it "returns an error if Profile image file name is too long" do + profile_image = fixture_file_upload("files/800x600.png", "image/png") + allow(profile_image).to receive(:original_filename).and_return("#{'a_very_long_filename' * 15}.png") + service = described_class.call(user, profile: {}, user: { profile_image: profile_image }) + + expect(service.success?).to be false + expect(service.error_message).to eq "filename too long - the max is 250 characters." + end end diff --git a/spec/system/user/user_edits_profile_spec.rb b/spec/system/user/user_edits_profile_spec.rb index 5e6f4de87..3cae26770 100644 --- a/spec/system/user/user_edits_profile_spec.rb +++ b/spec/system/user/user_edits_profile_spec.rb @@ -19,9 +19,9 @@ RSpec.describe "User edits their profile", type: :system do it "makes the 'Save Button' footer sticky once a field is filled in", js: true do expect(page).not_to have_css(".sticky") - fill_in "user[website_url]", with: "example.com" + fill_in "user[username]", with: "sloan" - find("#user_website_url").native.send_keys :tab # this un-focuses the filled-in field + find_field(id: "user[username]").native.send_keys :tab # this un-focuses the filled-in field expect(page).to have_css(".sticky") end diff --git a/spec/system/user_selects_looking_for_work_spec.rb b/spec/system/user_selects_looking_for_work_spec.rb index f30cfb877..d9d2e7120 100644 --- a/spec/system/user_selects_looking_for_work_spec.rb +++ b/spec/system/user_selects_looking_for_work_spec.rb @@ -4,17 +4,20 @@ RSpec.describe "Looking For Work", type: :system do let(:user) { create(:user) } before do - create(:tag, name: "hiring") - sign_in(user) - visit "/settings" + user.follow(create(:tag, name: "hiring")) + create(:profile_field, label: "Looking for work", input_type: :check_box) + Profile.refresh_attributes! end it "user selects looking for work and autofollows hiring tag", js: true do - page.check "Looking for work" - sidekiq_perform_enqueued_jobs do - click_button("Save") - end - expect(page).to have_text("Your profile was successfully updated") + allow(SiteConfig).to receive(:dev_to?).and_return(true) + + sign_in(user) + visit "/settings" + page.check("profile[looking_for_work]") + sidekiq_perform_enqueued_jobs { click_button("Save") } + + expect(page).to have_text("Your profile has been updated") expect(user.follows.count).to eq(1) end end