Skip to content

DEV: Add badges converter and importer steps #33019

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 1 commit 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
9 changes: 8 additions & 1 deletion migrations/config/intermediate_db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ output:

schema:
tables:
badges:
columns:
add:
- name: "existing_id"
datatype: "text"
exclude:
- "grant_count"
- "system"
user_emails:
columns:
include:
Expand Down Expand Up @@ -63,7 +71,6 @@ schema:
- "backup_metadata"
- "badge_groupings"
- "badge_types"
- "badges"
- "bookmarks"
- "categories"
- "categories_web_hooks"
Expand Down
24 changes: 24 additions & 0 deletions migrations/db/intermediate_db_schema/100-base-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,30 @@
-- update the "config/intermediate_db.yml" configuration file and then run
-- `bin/cli schema generate` to regenerate this file.

CREATE TABLE badges
(
original_id NUMERIC NOT NULL PRIMARY KEY,
allow_title BOOLEAN,
auto_revoke BOOLEAN,
badge_grouping_id NUMERIC,
badge_type_id NUMERIC NOT NULL,
created_at DATETIME NOT NULL,
description TEXT,
enabled BOOLEAN,
existing_id TEXT,
icon TEXT,
image_upload_id TEXT,
listable BOOLEAN,
long_description TEXT,
multiple_grant BOOLEAN,
name TEXT NOT NULL,
"query" TEXT,
show_in_post_header BOOLEAN,
show_posts BOOLEAN,
target_posts BOOLEAN,
"trigger" INTEGER
);

CREATE TABLE user_emails
(
email TEXT NOT NULL PRIMARY KEY,
Expand Down
63 changes: 63 additions & 0 deletions migrations/lib/converters/discourse/steps/badges.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

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

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

def items
@source_db.query <<~SQL
SELECT b.*,
u.id AS original_image_upload_id,
u.url AS image_upload_path,
u.original_filename AS image_upload_filename,
u.origin,
u.user_id AS image_upload_user_id
FROM badges b
LEFT JOIN uploads u ON b.image_upload_id = u.id
SQL
end

def process_item(item)
item[:existing_id] = item[:name] if item[:system]

image_upload =
if item[:original_image_upload_id].present?
IntermediateDB::Upload.create_for_file(
path: item[:image_upload_path],
filename: item[:image_upload_filename],
origin: item[:origin],
user_id: item[:image_upload_user_id],
)
end

IntermediateDB::Badge.create(
original_id: item[:id],
name: item[:name],
description: item[:description],
badge_type_id: item[:badge_type_id],
created_at: item[:created_at],
allow_title: item[:allow_title],
multiple_grant: item[:multiple_grant],
icon: item[:icon],
listable: item[:listable],
target_posts: item[:target_posts],
query: item[:query],
enabled: item[:enabled],
existing_id: item[:existing_id],
auto_revoke: item[:auto_revoke],
badge_grouping_id: item[:badge_grouping_id],
trigger: item[:trigger],
show_posts: item[:show_posts],
long_description: item[:long_description],
image_upload_id: image_upload&.id,
show_in_post_header: item[:show_in_post_header],
)
end
end
end
84 changes: 84 additions & 0 deletions migrations/lib/database/intermediate_db/badge.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# 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 Badge
SQL = <<~SQL
INSERT INTO badges (
original_id,
allow_title,
auto_revoke,
badge_grouping_id,
badge_type_id,
created_at,
description,
enabled,
existing_id,
icon,
image_upload_id,
listable,
long_description,
multiple_grant,
name,
"query",
show_in_post_header,
show_posts,
target_posts,
"trigger"
)
VALUES (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
SQL

def self.create(
original_id:,
allow_title: nil,
auto_revoke: nil,
badge_grouping_id: nil,
badge_type_id:,
created_at:,
description: nil,
enabled: nil,
existing_id: nil,
icon: nil,
image_upload_id: nil,
listable: nil,
long_description: nil,
multiple_grant: nil,
name:,
query: nil,
show_in_post_header: nil,
show_posts: nil,
target_posts: nil,
trigger: nil
)
::Migrations::Database::IntermediateDB.insert(
SQL,
original_id,
::Migrations::Database.format_boolean(allow_title),
::Migrations::Database.format_boolean(auto_revoke),
badge_grouping_id,
badge_type_id,
::Migrations::Database.format_datetime(created_at),
description,
::Migrations::Database.format_boolean(enabled),
existing_id,
icon,
image_upload_id,
::Migrations::Database.format_boolean(listable),
long_description,
::Migrations::Database.format_boolean(multiple_grant),
name,
query,
::Migrations::Database.format_boolean(show_in_post_header),
::Migrations::Database.format_boolean(show_posts),
::Migrations::Database.format_boolean(target_posts),
trigger,
)
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 @@ -3,5 +3,6 @@
module Migrations::Importer
module MappingType
USERS = 1
BADGES = 2
end
end
123 changes: 123 additions & 0 deletions migrations/lib/importer/steps/badges.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# frozen_string_literal: true

module Migrations::Importer::Steps
class Badges < ::Migrations::Importer::CopyStep
DEFAULT_ICON = "certificate"
DUPLICATE_SUFFIX = "_1"

requires_mapping :ids_by_name, "SELECT name, id FROM badges"
requires_set :existing_ids, "SELECT id FROM badges"
requires_set :existing_names, "SELECT name FROM badges"
requires_set :existing_badge_grouping_ids, "SELECT id FROM badge_groupings"
requires_set :existing_badge_type_ids, "SELECT id FROM badge_types"

column_names %i[
id
name
description
badge_type_id
created_at
updated_at
allow_title
multiple_grant
icon
listable
target_posts
query
enabled
auto_revoke
badge_grouping_id
trigger
show_posts
long_description
image_upload_id
show_in_post_header
]

store_mapped_ids true

total_rows_query <<~SQL, MappingType::BADGES
SELECT COUNT(*)
FROM badges b
LEFT JOIN mapped.ids mi ON b.original_id = mi.original_id AND mi.type = ?
WHERE mi.original_id IS NULL
SQL

rows_query <<~SQL, MappingType::BADGES
SELECT b.*
FROM badges b
LEFT JOIN mapped.ids mi ON b.original_id = mi.original_id AND mi.type = ?
WHERE mi.original_id IS NULL
GROUP BY b.original_id
ORDER BY b.ROWID
SQL

private

def transform_row(row)
if row[:existing_id].present?
row[:id] = if row[:existing_id].match?(/\A\d+\z/)
id = row[:existing_id].to_i
id if @existing_ids.include?(id)
else
@ids_by_name[row[:existing_id]]
end

return nil if row[:id].present?
end

badge_name = row[:name].dup
row[:name] = @existing_names.add?(badge_name) ? badge_name : deduplicate_name(badge_name)

row[:allow_title] ||= false
row[:multiple_grant] ||= false
row[:show_posts] ||= false
row[:show_in_post_header] ||= false
row[:target_posts] ||= false
row[:auto_revoke] = true if row[:auto_revoke].nil?
row[:enabled] = true if row[:enabled].nil?
row[:listable] = true if row[:listable].nil?

row[:icon] = DEFAULT_ICON if row[:icon].blank?
row[:trigger] = Badge::Trigger::None unless valid_trigger?(row[:trigger])

# TODO: Update these if/when we add import steps for badge groupings and badge types
# Current implementation expects the converter to set final values
row[:badge_grouping_id] = ensure_related_id(
row[:badge_grouping_id],
@existing_badge_grouping_ids,
BadgeGrouping::Other,
)
row[:badge_type_id] = ensure_related_id(
row[:badge_type_id],
@existing_badge_type_ids,
BadgeType::Silver,
)

# TODO: Probably validate the imported query, maybe in some other step
row[:query] = nil if !SiteSetting.enable_badge_sql? && row[:query].present?

# TODO: Resolve and include image_upload_id once have an uploads step

super
end

def deduplicate_name(name)
new_name = name + DUPLICATE_SUFFIX
new_name.next! until @existing_names.add?(new_name)

new_name
end

def ensure_related_id(value, allowed_set, default_value)
allowed_set.include?(value) ? value : default_value
end

def valid_trigger?(trigger)
return false if trigger.blank?

Badge::Trigger.is_none?(trigger) || Badge::Trigger.uses_user_ids?(trigger) ||
Badge::Trigger.uses_post_ids?(trigger)
end
end
end
5 changes: 5 additions & 0 deletions migrations/spec/lib/database/intermediate_db/badge_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

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