Skip to content

DEV: Add support for converting and importing user_fields #34295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions migrations/config/intermediate_db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ schema:
- "user_id"
- "created_at"
primary_key_column_names: [ "user_id" , "email" ]
user_field_options:
primary_key_column_names: [ "user_field_id", "value" ]
columns:
exclude:
- "id"
user_fields:
columns:
exclude:
- "field_type"
- "required"
user_options:
primary_key_column_names: [ "user_id" ]
users:
Expand Down Expand Up @@ -394,8 +404,6 @@ schema:
- "user_chat_thread_memberships"
- "user_custom_fields"
- "user_exports"
- "user_field_options"
- "user_fields"
- "user_histories"
- "user_ip_address_histories"
- "user_notification_schedules"
Expand Down
25 changes: 25 additions & 0 deletions migrations/db/intermediate_db_schema/100-base-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,31 @@ CREATE TABLE user_emails
PRIMARY KEY (user_id, email)
);

CREATE TABLE user_field_options
(
user_field_id NUMERIC NOT NULL,
value TEXT NOT NULL,
created_at DATETIME,
PRIMARY KEY (user_field_id, value)
);

CREATE TABLE user_fields
(
original_id NUMERIC NOT NULL PRIMARY KEY,
created_at DATETIME,
description TEXT NOT NULL,
editable BOOLEAN,
external_name TEXT,
external_type TEXT,
field_type_enum INTEGER NOT NULL,
name TEXT NOT NULL,
position INTEGER,
requirement INTEGER,
searchable BOOLEAN,
show_on_profile BOOLEAN,
show_on_user_card BOOLEAN
);

CREATE TABLE user_options
(
user_id NUMERIC NOT NULL PRIMARY KEY,
Expand Down
29 changes: 29 additions & 0 deletions migrations/lib/converters/discourse/steps/user_field_options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

module Migrations::Converters::Discourse
class UserFieldOptions < ::Migrations::Converters::Base::ProgressStep
attr_accessor :source_db

def max_progress
@source_db.count <<~SQL
SELECT COUNT(*) FROM user_field_options
SQL
end

def items
@source_db.query <<~SQL
SELECT *
FROM user_field_options
ORDER BY user_field_id
SQL
end

def process_item(item)
IntermediateDB::UserFieldOption.create(
user_field_id: item[:user_field_id],
value: item[:value],
created_at: item[:created_at],
)
end
end
end
39 changes: 39 additions & 0 deletions migrations/lib/converters/discourse/steps/user_fields.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module Migrations::Converters::Discourse
class UserFields < ::Migrations::Converters::Base::ProgressStep
attr_accessor :source_db

def max_progress
@source_db.count <<~SQL
SELECT COUNT(*) FROM user_fields
SQL
end

def items
@source_db.query <<~SQL
SELECT *
FROM user_fields
ORDER BY id
SQL
end

def process_item(item)
IntermediateDB::UserField.create(
original_id: item[:id],
created_at: item[:created_at],
description: item[:description],
editable: item[:editable],
external_name: item[:external_name],
external_type: item[:external_type],
field_type_enum: item[:field_type_enum],
name: item[:name],
position: item[:position],
requirement: item[:requirement],
searchable: item[:searchable],
show_on_profile: item[:show_on_profile],
show_on_user_card: item[:show_on_user_card],
)
end
end
end
63 changes: 63 additions & 0 deletions migrations/lib/database/intermediate_db/user_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

# This file is auto-generated from the IntermediateDB schema. To make changes,
# update the "config/intermediate_db.yml" configuration file and then run
# `bin/cli schema generate` to regenerate this file.

module Migrations::Database::IntermediateDB
module UserField
SQL = <<~SQL
INSERT INTO user_fields (
original_id,
created_at,
description,
editable,
external_name,
external_type,
field_type_enum,
name,
position,
requirement,
searchable,
show_on_profile,
show_on_user_card
)
VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
SQL

def self.create(
original_id:,
created_at: nil,
description:,
editable: nil,
external_name: nil,
external_type: nil,
field_type_enum:,
name:,
position: nil,
requirement: nil,
searchable: nil,
show_on_profile: nil,
show_on_user_card: nil
)
::Migrations::Database::IntermediateDB.insert(
SQL,
original_id,
::Migrations::Database.format_datetime(created_at),
description,
::Migrations::Database.format_boolean(editable),
external_name,
external_type,
field_type_enum,
name,
position,
requirement,
::Migrations::Database.format_boolean(searchable),
::Migrations::Database.format_boolean(show_on_profile),
::Migrations::Database.format_boolean(show_on_user_card),
)
end
end
end
29 changes: 29 additions & 0 deletions migrations/lib/database/intermediate_db/user_field_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# frozen_string_literal: true

# This file is auto-generated from the IntermediateDB schema. To make changes,
# update the "config/intermediate_db.yml" configuration file and then run
# `bin/cli schema generate` to regenerate this file.

module Migrations::Database::IntermediateDB
module UserFieldOption
SQL = <<~SQL
INSERT INTO user_field_options (
user_field_id,
value,
created_at
)
VALUES (
?, ?, ?
)
SQL

def self.create(user_field_id:, value:, created_at: nil)
::Migrations::Database::IntermediateDB.insert(
SQL,
user_field_id,
value,
::Migrations::Database.format_datetime(created_at),
)
end
end
end
1 change: 1 addition & 0 deletions migrations/lib/importer/mapping_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ module MappingType
BADGES = 2
CATEGORIES = 3
GROUPS = 4
USER_FIELDS = 5
UPLOADS = 10
end
end
41 changes: 41 additions & 0 deletions migrations/lib/importer/steps/user_field_options.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

module Migrations::Importer::Steps
class UserFieldOptions < ::Migrations::Importer::CopyStep
depends_on :user_fields

requires_set :existing_user_field_options, "SELECT user_field_id, value FROM user_field_options"

column_names %i[user_field_id value created_at updated_at]

total_rows_query <<~SQL, MappingType::USER_FIELDS
SELECT COUNT(*)
FROM user_field_options
JOIN mapped.ids mapped_user_field
ON user_field_options.user_field_id = mapped_user_field.original_id
AND mapped_user_field.type = ?
SQL

rows_query <<~SQL, MappingType::USER_FIELDS
SELECT user_field_options.*,
mapped_user_field.discourse_id AS discourse_user_field_id
FROM user_field_options
JOIN mapped.ids mapped_user_field
ON user_field_options.user_field_id = mapped_user_field.original_id
AND mapped_user_field.type = ?
ORDER BY user_field_options.user_field_id, user_field_options.value
SQL

private

def transform_row(row)
user_field_id = row[:discourse_user_field_id]

return nil unless @existing_user_field_options.add?(user_field_id, row[:value])

row[:user_field_id] = user_field_id

super
end
end
end
117 changes: 117 additions & 0 deletions migrations/lib/importer/steps/user_fields.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# frozen_string_literal: true

module Migrations::Importer::Steps
class UserFields < ::Migrations::Importer::CopyStep
include ::HasSanitizableFields

SANITIZER_ATTRIBUTES = %w[target].freeze
DEFAULT_POSITION = 0
REQUIREMENTS = UserField.requirements.values.to_set.freeze
DEFAULT_REQUIREMENT = UserField.requirements[:optional]
REQUIRED_FOR_ALL = UserField.requirements[:for_all_users]
FIELD_TYPES = UserField.field_type_enums.values.to_set.freeze
DEFAULT_FIELD_TYPE = UserField.field_type_enums[:text]

requires_mapping :existing_user_field_by_name, "SELECT LOWER(name), id FROM user_fields"

column_names %i[
id
created_at
description
editable
external_name
external_type
field_type_enum
name
position
requirement
searchable
show_on_profile
show_on_user_card
updated_at
]

store_mapped_ids true

total_rows_query <<~SQL, MappingType::USER_FIELDS
SELECT COUNT(*)
FROM user_fields
LEFT JOIN mapped.ids mapped_user_field
ON user_fields.original_id = mapped_user_field.original_id
AND mapped_user_field.type = ?
WHERE mapped_user_field.original_id IS NULL
SQL

rows_query <<~SQL, MappingType::USER_FIELDS
SELECT user_fields.*
FROM user_fields
LEFT JOIN mapped.ids mapped_user_field
ON user_fields.original_id = mapped_user_field.original_id
AND mapped_user_field.type = ?
WHERE mapped_user_field.original_id IS NULL
ORDER BY user_fields.original_id
SQL

def initialize(intermediate_db, discourse_db, shared_data)
super

@required_fields_version_bumped = false
end

private

def transform_row(row)
name = row[:name]
name_lower = name.downcase

if (existing_id = @existing_user_field_by_name[name_lower])
row[:id] = existing_id

return nil
end

description = row[:description]

if description.empty?
puts " User field '#{name}' description cannot be empty"

return nil
end

row[:editable] ||= false
row[:show_on_profile] ||= false
row[:show_on_user_card] ||= false
row[:searchable] ||= false

row[:position] ||= DEFAULT_POSITION
row[:description] = sanitize_field(description, additional_attributes: SANITIZER_ATTRIBUTES)

row[:requirement] = ensure_valid_value(
value: row[:requirement],
allowed_set: REQUIREMENTS,
default_value: DEFAULT_REQUIREMENT,
)
row[:field_type_enum] = ensure_valid_value(
value: row[:field_type_enum],
allowed_set: FIELD_TYPES,
default_value: DEFAULT_FIELD_TYPE,
)

super
end

def after_commit_of_inserted_rows(rows)
super

if !@required_fields_version_bumped &&
rows.any? { |row| row[:requirement] == REQUIRED_FOR_ALL }
DB.exec(<<~SQL)
INSERT INTO user_required_fields_versions (created_at, updated_at)
VALUES (NOW(), NOW())
SQL

@required_fields_version_bumped = true
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

RSpec.describe ::Migrations::Database::IntermediateDB::UserFieldOption do
it_behaves_like "a database entity"
end
Loading