[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 <jacobherringtondeveloper@gmail.com>

* Update docs/technical-overview/compatibility.md

Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com>

* Update docs/technical-overview/compatibility.md

Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com>

* Update docs/technical-overview/compatibility.md

Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com>

* 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 <ridhwana.khan16@gmail.com>
Co-authored-by: Andrew Bone <AndrewB05@gmail.com>
Co-authored-by: Vaidehi Joshi <vaidehi.sj@gmail.com>
Co-authored-by: Julianna Tetreault <32834804+juliannatetreault@users.noreply.github.com>
Co-authored-by: Robin Gagnon <me@reobin.dev>
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 <rt.trojanowski@gmail.com>
Co-authored-by: Mac Siri <mac@dev.to>
Co-authored-by: Josh Puetz <josh@dev.to>
Co-authored-by: Ben Halpern <bendhalpern@gmail.com>
Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com>
Co-authored-by: irmela <irmela@berlin-coding.de>
Co-authored-by: ludwiczakpawel <ludwiczakpawel@gmail.com>
This commit is contained in:
Michael Kohl 2020-10-20 20:18:13 +07:00 committed by GitHub
parent 14f0f03aba
commit 9377eee4f5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 214 additions and 269 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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/..."

View file

@ -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

View file

@ -15,26 +15,18 @@
</div>
<% 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| %>
<div class="crayons-card mb-6 grid grid-flow-row gap-6 p-6">
<h2>User</h2>
<div class="crayons-field">
<%= f.label :email, class: "crayons-field__label" %>
<%= f.text_field :email, class: "crayons-textfield", placeholder: "john.doe@example.com" %>
</div>
<div class="crayons-field crayons-field--checkbox">
<%= 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 %>
</div>
<div class="crayons-field">
<%= f.label :username, class: "crayons-field__label" %>
<%= f.text_field :username, maxlength: 30, class: "crayons-textfield", placeholder: "johndoe" %>
</div>
<div class="crayons-field">
<%= 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 %>
</div>
<div class="crayons-field">
@ -43,158 +35,32 @@
<div class="flex items-center">
<% if @user.profile_image_url.present? %>
<span class="crayons-avatar crayons-avatar--xl mr-2"><img alt="<%= @user.username %> profile image" src="<%= Images::Profile.call(@user.profile_image_url, length: 50) %>" class="crayons-avatar__image" /></span>
<%= 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 %>
</div>
</div>
<div class="crayons-field">
<%= f.label :website_url, class: "crayons-field__label" %>
<%= f.text_field :website_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://yoursite.com" %>
</div>
<div class="crayons-field">
<%= f.label :summary, class: "crayons-field__label" %>
<%= f.text_area :summary, maxlength: 200, class: "crayons-textfield", placeholder: "A short bio...", rows: 3 %>
</div>
<div class="crayons-field">
<%= f.label :location, class: "crayons-field__label" %>
<%= f.text_field :location, maxlength: 100, class: "crayons-textfield", placeholder: "Halifax, Nova Scotia" %>
</div>
<div class="crayons-field">
<%= f.label :education, class: "crayons-field__label" %>
<%= f.text_field :education, maxlength: 100, class: "crayons-textfield" %>
</div>
<div class="crayons-field">
<%= f.label :employer_name, class: "crayons-field__label" %>
<%= f.text_field :employer_name, maxlength: 100, class: "crayons-textfield", placeholder: "Acme Inc." %>
</div>
<div class="crayons-field">
<%= f.label :employer_url, class: "crayons-field__label" %>
<%= f.text_field :employer_url, maxlength: 100, class: "crayons-textfield", placeholder: "https://dev.com" %>
</div>
<div class="crayons-field">
<%= f.label :employment_title, class: "crayons-field__label" %>
<%= f.text_field :employment_title, maxlength: 100, class: "crayons-textfield", placeholder: "Junior Frontend Engineer" %>
</div>
<div class="crayons-field">
<%= f.label :mostly_work_with, "Skills/Languages", class: "crayons-field__label" %>
<%= f.text_area :mostly_work_with, maxlength: 500, class: "crayons-textfield", rows: 5 %>
<p class="crayons-field__description">What tools and languages are you most experienced with? Are you specialized or more of a generalist?</p>
</div>
<div class="crayons-field">
<%= f.label :currently_learning, "I'm getting into", class: "crayons-field__label" %>
<%= f.text_area :currently_learning, maxlength: 500, class: "crayons-textfield", rows: 5 %>
<p class="crayons-field__description">What are you learning right now? What are the new tools and languages you're picking up in <%= Time.zone.now.year %>?</p>
</div>
<div class="crayons-field">
<%= 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 %>
<p class="crayons-field__description">What projects are currently occupying most of your time?</p>
</div>
<div class="crayons-field">
<%= f.label :available_for, class: "crayons-field__label" %>
<%= f.text_area :available_for, maxlength: 500, class: "crayons-textfield", rows: 5 %>
<p class="crayons-field__description">What kinds of collaborations or discussions are you available for? What's a good reason to say <em>Hey!</em> to you these days?</p>
</div>
</div>
<div class="crayons-card mb-6 grid gap-4 p-6">
<h2>Branding</h2>
<%= render partial: "shared/logo_design", locals: { f: f, account: @user } %>
<% ProfileFieldGroup.non_empty_groups.each do |group| %>
<div class="crayons-card mb-6 grid grid-flow-row gap-6 p-6">
<h2><%= group.name %></h2>
<% if group.description.present? %>
<div class="color-base-40"><%= group.description %></div>
<% end %>
<% group.profile_fields.each do |field| %>
<div class="crayons-field <%= "crayons-field--check_box" if field.input_type == "check_box" %>">
<div><%= field.label %></div>
<% 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 %>
</div>
<% end %>
</div>
<% end %>
<div class="save-footer crayons-card mb-6 grid p-6">
<button type="submit" class="crayons-btn">Save Profile Information</button>
</div>
<div class="crayons-card mb-6 grid gap-4 p-6">
<h2>Work opportunities settings</h2>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :looking_for_work, class: "crayons-checkbox" %>
<%= f.label :looking_for_work, class: "crayons-field__label" %>
</div>
<div class="crayons-field crayons-field--checkbox">
<%= 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" %>
</div>
<div class="crayons-field crayons-field--checkbox">
<%= f.check_box :contact_consent, class: "crayons-checkbox" %>
<%= f.label :contact_consent, "Recruiters can contact me about job opportunities", class: "crayons-field__label" %>
</div>
</div>
<div class="crayons-card mb-6 grid gap-6 p-6">
<h2>Links</h2>
<div class="crayons-field">
<%= f.label :facebook_url, "Facebook profile URL", class: "crayons-field__label" %>
<%= f.url_field :facebook_url, class: "crayons-textfield", placeholder: "https://facebook.com/..." %>
</div>
<div class="crayons-field">
<%= f.label :youtube_url, "Youtube URL", class: "crayons-field__label" %>
<%= f.url_field :youtube_url, class: "crayons-textfield", placeholder: "https://www.youtube.com/channel/..." %>
</div>
<div class="crayons-field">
<%= 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/..." %>
</div>
<div class="crayons-field">
<%= f.label :linkedin_url, "LinkedIn profile URL", class: "crayons-field__label" %>
<%= f.url_field :linkedin_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :behance_url, "Behance profile URL", class: "crayons-field__label" %>
<%= f.url_field :behance_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :dribbble_url, "Dribbble profile URL", class: "crayons-field__label" %>
<%= f.url_field :dribbble_url, class: "crayons-textfield", placeholder: "https://dribbble.com/..." %>
</div>
<div class="crayons-field">
<%= f.label :medium_url, "Medium profile URL", class: "crayons-field__label" %>
<%= f.url_field :medium_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :gitlab_url, "GitLab profile URL", class: "crayons-field__label" %>
<%= f.url_field :gitlab_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :instagram_url, "Instagram profile URL", class: "crayons-field__label" %>
<%= f.url_field :instagram_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :mastodon_url, "Mastodon profile URL", class: "crayons-field__label" %>
<%= f.url_field :mastodon_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
<div class="crayons-field">
<%= f.label :twitch_url, "Twitch profile URL", class: "crayons-field__label" %>
<%= f.url_field :twitch_url, class: "crayons-textfield", placeholder: "https://..." %>
</div>
</div>
<div class="save-footer crayons-card mb-6 grid gap-6 p-6">
<%= f.hidden_field :tab, value: @tab %>
<div>
<button type="submit" class="crayons-btn">Save</button>
</div>
</div>
<% end %>

View file

@ -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

1 Display email on profile check_box Basic
8 StackOverflow URL text_field https://stackoverflow.com/users/... Links
9 LinkedIn URL text_field https://www.linkedin.com/in/... Links
10 Behance URL text_field https://www.behance.net/... Links
11 Dribble URL Dribbble URL text_field https://dribble.com/... https://dribbble.com/... Links
12 Medium URL text_field https://medium.com/@... Links
13 GitLab URL text_field https://gitlab.com/... Links
14 Instagram URL text_field https://www.instagram.com/... Links

View file

@ -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

View file

@ -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

View file

@ -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" } }

View file

@ -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

View file

@ -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

View file

@ -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