Profile generalization: cleanup (#12947)

* Remove hardcoded instances of education field

* WIP Access display_email_on_profile via User Settings

* Remove unused profile column

* WIP Fix reference to bg_color_hex and text_color_hex

* WIP fix issues revealed by systems specs

* WIP fix issues revealed by services specs

* WIP Fix failing tests

* WIP Fix spec failures

* wip

* Move two attributes from controller to decorator

* Update comment

* Match user settings changes

* More consistently use user.tag_line

Even before the profile changes we sometimes used the user.summary
attribute directly but used the user.tag_line method in other places.

* Remove delegation, rename inline concern

* Drop profile columns from users table

* Remove duplicated work display from header

* Update work profile field handling

* Update DUS + spec

* Delegate more carefully

* Update delegation guard

* Adapt for removed delegation

* Undo accidental schema changes

* Fix seeds

* Remove accidentaly change

* Fix User#processed_website_url

* Update guard clause

* Update profile card content

* Add Organization#profile

* Be more conservative with profile fields

* Spec fixes round 1

* Fix typo

* Update spec

* Limit number of header fields and update card content

* Decorate correct model

* Update factory

* Update schema.rb

* Fix validation

* How bad could this possibly be?

* Pretty bad, nevermind

* Remove obsolete code

* Reset profile fields during test runs

* Move profile fields back to before(:suite)

* Spec fixes

* Remove accidentally re-added files

* More spec fixes

* Specs

* Change User#tag_keywords_for_search

* More spec fixes

* Add comment

* Undo accidental schema changes

* Attempt spec fix

* Remove fix attempt

* Fix e2e test

* Update spec

* Remove guard clause

* Remove hardcoded instances of education field

* WIP Access display_email_on_profile via User Settings

* Remove unused profile column

* WIP fix issues revealed by systems specs

* WIP fix issues revealed by services specs

* WIP Fix failing tests

* WIP Fix spec failures

* wip

* Move two attributes from controller to decorator

* Update comment

* More consistently use user.tag_line

Even before the profile changes we sometimes used the user.summary
attribute directly but used the user.tag_line method in other places.

* Remove education

* Add comment

* WIP

* Clean up mostly_work_with

* WIP

* Update work profile field handling

* More work-related changes

* Remove settings_only from display_area enum

* Remove quickfix from _metadata partial

* Remove special attributes

* Remove leftover spec

* Retrieve location from profile, not user

* Profile.special_attributes no longer exists

* Update specs

* More spec fixes

* Update UsersController

* Update UsersController and spec

* Fix e2e seeds

* Minor cleanup

* More e2e seed fixes

* Fix profile field CSV

* Fix e2e seeds

* Move one more attribute in e2e seeds

* Remove duplicate line

* Clear inputs before typing in them

* Fix formatting issues

* Profiles::Update -> Users::Update

* Remove RegistrationsController#resolve_profile_field_issues

* Fix schema.rb

* More cleanup

* Fix specs

* Fix remaining spec

Co-authored-by: Jacob Herrington <jacobherringtondeveloper@gmail.com>
This commit is contained in:
Michael Kohl 2021-08-20 21:36:02 +07:00 committed by GitHub
parent 19db4b0287
commit 08d82e7256
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 66 additions and 92 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View file

@ -273,5 +273,5 @@
.personal-template-button:not(.active),
.moderator-template-button:not(.active) {
@extend .crayons-btn--outlined;
@extend .crayons-btn--outlined;
}

View file

@ -33,7 +33,7 @@ module Api
num = [per_page, 1000].min
page = params[:page] || 1
@users = @organization.users.select(USERS_FOR_SERIALIZATION).page(page).per(num)
@users = @organization.users.joins(:profile).select(USERS_FOR_SERIALIZATION).page(page).per(num)
end
def listings

View file

@ -30,6 +30,7 @@ module Api
.decorate
@users_by_id = User
.joins(:profile)
.select(UsersController::SHOW_ATTRIBUTES_FOR_SERIALIZATION)
.find(articles.map(&:user_id))
.index_by(&:id)

View file

@ -10,7 +10,7 @@ module Api
].freeze
def show
relation = User.select(SHOW_ATTRIBUTES_FOR_SERIALIZATION)
relation = User.joins(:profile).select(SHOW_ATTRIBUTES_FOR_SERIALIZATION)
@user = if params[:id] == "by_username"
relation.find_by!(username: params[:url])

View file

@ -5,7 +5,7 @@ class ProfilesController < ApplicationController
ALLOWED_USERS_SETTING_PARAMS = %i[display_email_on_profile brand_color1 brand_color2].freeze
def update
update_result = Profiles::Update.call(current_user, update_params)
update_result = Users::Update.call(current_user, update_params)
if update_result.success?
flash[:settings_notice] = "Your profile has been updated"
redirect_to user_settings_path

View file

@ -14,8 +14,6 @@ class RegistrationsController < Devise::RegistrationsController
def create
authorize(params, policy_class: RegistrationPolicy)
resolve_profile_field_issues
unless recaptcha_verified?
flash[:notice] = "You must complete the recaptcha ✅"
return redirect_to new_user_registration_path(state: "email_signup")
@ -58,16 +56,6 @@ class RegistrationsController < Devise::RegistrationsController
end
end
def resolve_profile_field_issues
# Run this data update script when in a state of "first user" in the event
# that we are in a state where this was not already run.
# This is likely only temporarily needed.
return unless Settings::General.waiting_on_first_user
csv = Rails.root.join("lib/data/dev_profile_fields.csv")
ProfileFields::ImportFromCsv.call(csv)
end
def check_allowed_email(resource)
domain = resource.email.split("@").last
allow_list = Settings::Authentication.allowed_registration_email_domains

View file

@ -180,7 +180,7 @@ class UsersController < ApplicationController
user_params.merge!(params[:user].permit(ALLOWED_USER_PARAMS))
end
update_result = Profiles::Update.call(current_user, user: user_params, profile: profile_params)
update_result = Users::Update.call(current_user, user: user_params, profile: profile_params)
render_update_response(update_result.success?, update_result.errors_as_sentence)
end

View file

@ -35,18 +35,13 @@ class User < ApplicationRecord
youtube_url
].freeze
INACTIVE_PROFILE_COLUMNS = %w[
bg_color_hex
text_color_hex
email_public
].freeze
self.ignored_columns = PROFILE_COLUMNS
# NOTE: @citizen428 This is temporary code during profile migration and will
# be removed.
concerning :ProfileMigration do
# NOTE: we are using an inline module to keep profile related things together.
concerning :Profiles do
included do
has_one :profile, dependent: :destroy
# NOTE: There are rare cases were we want to skip this callback, primarily
# in tests. `skip_callback` modifies global state, which is not thread-safe
# and can cause hard to track down bugs. We use an instance-level attribute
@ -55,14 +50,6 @@ class User < ApplicationRecord
# All new users should automatically have a profile
after_create_commit -> { Profile.create(user: self) }, unless: :_skip_creating_profile
# Getters and setters for unmapped profile attributes
PROFILE_COLUMNS.each do |column|
next if INACTIVE_PROFILE_COLUMNS.include?(column)
next unless column.in?(Profile.attributes)
delegate column, "#{column}=", to: :profile, allow_nil: true
end
end
end
@ -88,7 +75,6 @@ class User < ApplicationRecord
acts_as_followable
acts_as_follower
has_one :profile, dependent: :destroy
has_one :notification_setting, class_name: "Users::NotificationSetting", dependent: :destroy
has_one :setting, class_name: "Users::Setting", dependent: :destroy
@ -264,9 +250,6 @@ class User < ApplicationRecord
before_destroy :unsubscribe_from_newsletters, prepend: true
before_destroy :destroy_follows, prepend: true
# NOTE: @citizen428 Temporary while migrating to generalized profiles
after_save { |user| user.profile&.save if user.profile&.changed? }
after_create_commit :send_welcome_notification
after_commit :subscribe_to_mailchimp_newsletter

View file

@ -52,7 +52,7 @@ module Users
end
def relation_as_array(relation, limit:)
relation = relation.select(attributes_to_select) if attributes_to_select.any?
relation = relation.joins(:profile).select(attributes_to_select) if attributes_to_select.any?
relation.order(updated_at: :desc).limit(limit).to_a
end

View file

@ -1,6 +1,4 @@
# TODO: This should probably become `Users::Update` as we're dealing with core
# user attributes, profile attributes and user settings.
module Profiles
module Users
# Deals with updates that affect fields on +Profile+ and/or +User+ in a transparent way.
class Update
using HashAnyKey
@ -15,11 +13,11 @@ module Profiles
# and/or user attributes to update. The corresponding has keys are +:user+ and
# +:profile+ respectively.
# @example
# Profiles::Update.call(
# Users::Update.call(
# current_user,
# profile: { website_url: "https://example.com" },
# )
# @return [Profiles::Update] a class instance that can be used for success checks
# @return [Users::Update] a class instance that can be used for success checks
def self.call(user, updated_attributes = {})
new(user, updated_attributes).call
end

View file

@ -11,9 +11,9 @@ class ProfileValidator < ActiveModel::Validator
}.with_indifferent_access.freeze
def validate(record)
# NOTE: @citizen428 The summary is a base profile field, which we add to all
# new Forem instances, so it should be safe to validate. The method itself
# also guards against the field's absence.
# NOTE: The summary is a base profile field, which we add to all new Forem
# instances, so it should be safe to validate. The method itself also guards
# against the field's absence.
record.errors.add(:summary, "is too long") if summary_too_long?(record)
ProfileField.all.each do |field|

View file

@ -6,7 +6,7 @@
<%= sanitized_sidebar title.titleize %>
</strong>
<p class="crayons-definition__value">
<%= sanitized_sidebar value %>
<%= sanitized_sidebar value %>
</p>
</div>
<% end %>

View file

@ -78,9 +78,6 @@
</div>
<% ProfileFieldGroup.non_empty_groups.each do |group| %>
<% next if group.name == "Basic" # TODO: @citizen428 Remove this after user settings work (email field) %>
<% next if group.name == "Links" # TODO: [@jacobherrington] Remove this when we drop social links %>
<% next if group.name == "Branding" # TODO: [@msarit @jacobherrington] May be further refactored later as part of Profiles work %>
<div class="crayons-card crayons-card--content-rows">
<h2><%= group.name %></h2>
<% if group.description.present? %>

View file

@ -55,13 +55,13 @@ module.exports = function (api) {
'@babel/plugin-proposal-private-property-in-object',
{
loose: true,
}
},
],
[
'@babel/plugin-proposal-private-methods',
{
loose: true,
}
},
],
[
'@babel/plugin-transform-react-jsx',

View file

@ -0,0 +1,33 @@
class RemoveProfileColumnsFromUser < ActiveRecord::Migration[6.0]
def change
# rubocop:disable Metrics/Blocklength
safety_assured do
remove_column :users, :available_for
remove_column :users, :behance_url
remove_column :users, :bg_color_hex
remove_column :users, :currently_hacking_on
remove_column :users, :currently_learning
remove_column :users, :dribbble_url
remove_column :users, :education
remove_column :users, :email_public
remove_column :users, :employer_name
remove_column :users, :employer_url
remove_column :users, :employment_title
remove_column :users, :facebook_url
remove_column :users, :gitlab_url
remove_column :users, :instagram_url
remove_column :users, :linkedin_url
remove_column :users, :location
remove_column :users, :mastodon_url
remove_column :users, :medium_url
remove_column :users, :mostly_work_with
remove_column :users, :stackoverflow_url
remove_column :users, :summary
remove_column :users, :text_color_hex
remove_column :users, :twitch_url
remove_column :users, :website_url
remove_column :users, :youtube_url
end
# rubocop:enable Metrics/Blocklength
end
end

View file

@ -1276,10 +1276,7 @@ ActiveRecord::Schema.define(version: 2021_07_20_042422) do
create_table "users", force: :cascade do |t|
t.string "apple_username"
t.integer "articles_count", default: 0, null: false
t.string "available_for"
t.integer "badge_achievements_count", default: 0, null: false
t.string "behance_url"
t.string "bg_color_hex"
t.bigint "blocked_by_count", default: 0, null: false
t.bigint "blocking_others_count", default: 0, null: false
t.boolean "checked_code_of_conduct", default: false
@ -1292,19 +1289,10 @@ ActiveRecord::Schema.define(version: 2021_07_20_042422) do
t.integer "credits_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.inet "current_sign_in_ip"
t.string "currently_hacking_on"
t.string "currently_learning"
t.string "dribbble_url"
t.string "education"
t.string "email"
t.boolean "email_public", default: false
t.string "employer_name"
t.string "employer_url"
t.string "employment_title"
t.string "encrypted_password", default: "", null: false
t.boolean "export_requested", default: false
t.datetime "exported_at"
t.string "facebook_url"
t.string "facebook_username"
t.integer "failed_attempts", default: 0
t.datetime "feed_fetched_at", default: "2017-01-01 05:00:00"
@ -1313,8 +1301,6 @@ ActiveRecord::Schema.define(version: 2021_07_20_042422) do
t.integer "following_users_count", default: 0, null: false
t.datetime "github_repos_updated_at", default: "2017-01-01 05:00:00"
t.string "github_username"
t.string "gitlab_url"
t.string "instagram_url"
t.datetime "invitation_accepted_at"
t.datetime "invitation_created_at"
t.integer "invitation_limit"
@ -1333,13 +1319,8 @@ ActiveRecord::Schema.define(version: 2021_07_20_042422) do
t.datetime "last_sign_in_at"
t.inet "last_sign_in_ip"
t.datetime "latest_article_updated_at"
t.string "linkedin_url"
t.string "location"
t.datetime "locked_at"
t.string "mastodon_url"
t.string "medium_url"
t.integer "monthly_dues", default: 0
t.string "mostly_work_with"
t.string "name"
t.string "old_old_username"
t.string "old_username"
@ -1363,21 +1344,15 @@ ActiveRecord::Schema.define(version: 2021_07_20_042422) do
t.integer "sign_in_count", default: 0, null: false
t.string "signup_cta_variant"
t.integer "spent_credits_count", default: 0, null: false
t.string "stackoverflow_url"
t.string "stripe_id_code"
t.integer "subscribed_to_user_subscriptions_count", default: 0, null: false
t.text "summary"
t.string "text_color_hex"
t.string "twitch_url"
t.string "twitter_username"
t.string "unconfirmed_email"
t.string "unlock_token"
t.integer "unspent_credits_count", default: 0, null: false
t.datetime "updated_at", null: false
t.string "username"
t.string "website_url"
t.datetime "workshop_expiration"
t.string "youtube_url"
t.index "to_tsvector('simple'::regconfig, COALESCE((name)::text, ''::text))", name: "index_users_on_name_as_tsvector", using: :gin
t.index "to_tsvector('simple'::regconfig, COALESCE((username)::text, ''::text))", name: "index_users_on_username_as_tsvector", using: :gin
t.index ["apple_username"], name: "index_users_on_apple_username"

View file

@ -56,7 +56,7 @@ import { tagPropTypes } from '../../../components/common-prop-types';
const SomeComponentUsingTags = ({ tags = [] }) => (
<ul>
{tags.map(tag => (
{tags.map((tag) => (
<li key={tag.id}>{tag.name}</li>
))}
</ul>

View file

@ -290,8 +290,8 @@ There are currently two work-arounds.
### WSL2 and End to End Tests
In order to run Cypress Test Runner, a few extra setup steps are required to run GUIs
natively inside of WSL2.
In order to run Cypress Test Runner, a few extra setup steps are required to run
GUIs natively inside of WSL2.
[This guide](https://nickymeuleman.netlify.app/blog/gui-on-wsl2-cypress) explains
how to use graphical user interfaces like Cypress inside WSL2.
[This guide](https://nickymeuleman.netlify.app/blog/gui-on-wsl2-cypress)
explains how to use graphical user interfaces like Cypress inside WSL2.

View file

@ -4,6 +4,8 @@ title: Self Hosting Forem
## Hosting your own Forem instance
Visit [the official Forem Self-Host playbook](https://github.com/forem/selfhost) in order to host on DigitalOcean, AWS, Google Cloud, or bare metal.
Visit [the official Forem Self-Host playbook](https://github.com/forem/selfhost)
in order to host on DigitalOcean, AWS, Google Cloud, or bare metal.
If you intend to stand up a Forem, we _strongly_ encourage you to join [forem.dev](https://forem.dev) to take part and keep up with changes. 🌱
If you intend to stand up a Forem, we _strongly_ encourage you to join
[forem.dev](https://forem.dev) to take part and keep up with changes. 🌱

View file

@ -13,7 +13,7 @@ RSpec.describe NavigationLink, type: :model do
it "validates the icon" do
navigation_link.icon = "test.png"
expect(navigation_link).not_to be_valid
navigation_link.icon = "<svg foo='bar'>"
expect(navigation_link).to be_valid

View file

@ -99,8 +99,7 @@ RSpec.describe "UserProfiles", type: :request do
end
it "does not render special display header elements naively" do
user.profile.location = "hawaii"
user.save
user.profile.update(location: "hawaii")
get "/#{user.username}"
# Does not include the word, but does include the SVG
expect(response.body).not_to include "<p>Location</p>"

View file

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe Profiles::Update, type: :service do
RSpec.describe Users::Update, type: :service do
def sidekiq_assert_resave_article_worker(user, &block)
sidekiq_assert_enqueued_with(
job: Users::ResaveArticlesWorker,