このコミットが含まれているのは:
@@ -25,7 +25,8 @@ class PostImportGoogleSheetsFetcher
|
|||||||
|
|
||||||
response.body
|
response.body
|
||||||
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed => e
|
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed => e
|
||||||
raise ArgumentError, "スプレッドシートを取得できませんでした.ウェブ公開 URL を確認するか、ファイル保存又はコピー貼付けを使用してください: #{ e.message }"
|
Rails.logger.info("post_import_google_sheets_fetch_failure #{ { error: e.class.name, message: e.message }.to_json }")
|
||||||
|
raise ArgumentError, 'スプレッドシートを取得できませんでした.ウェブ公開 URL を確認するか、ファイル保存又はコピー貼付けを使用してください.'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -122,7 +122,8 @@ class PostImportPreviewer
|
|||||||
warnings << 'サムネールを取得できませんでした.' if data['thumbnail_base'].blank?
|
warnings << 'サムネールを取得できませんでした.' if data['thumbnail_base'].blank?
|
||||||
[data.compact, warnings]
|
[data.compact, warnings]
|
||||||
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed, Preview::HttpFetcher::ResponseTooLarge => e
|
rescue Preview::UrlSafety::UnsafeUrl, Preview::HttpFetcher::FetchFailed, Preview::HttpFetcher::ResponseTooLarge => e
|
||||||
[{ }, ["メタデータを取得できませんでした: #{ e.message }"]]
|
Rails.logger.info("post_import_metadata_fetch_failure #{ { error: e.class.name, message: e.message }.to_json }")
|
||||||
|
[{ }, ['メタデータを取得できませんでした.']]
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_preview_tags raw, errors, warnings
|
def validate_preview_tags raw, errors, warnings
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class PostImportSourceParser
|
|||||||
MAX_ROWS = 100
|
MAX_ROWS = 100
|
||||||
MAX_BYTES = 1.megabyte
|
MAX_BYTES = 1.megabyte
|
||||||
MAX_CELL_BYTES = 20.kilobytes
|
MAX_CELL_BYTES = 20.kilobytes
|
||||||
|
MAX_COLUMNS = 50
|
||||||
|
|
||||||
def initialize source:, format:, has_header: false, json_path: nil
|
def initialize source:, format:, has_header: false, json_path: nil
|
||||||
@source = source.to_s
|
@source = source.to_s
|
||||||
@@ -28,8 +29,9 @@ class PostImportSourceParser
|
|||||||
headers = @has_header ? table.shift : nil
|
headers = @has_header ? table.shift : nil
|
||||||
ensure_limits!(table)
|
ensure_limits!(table)
|
||||||
width = table.map(&:length).max.to_i
|
width = table.map(&:length).max.to_i
|
||||||
|
raise ArgumentError, "列数は #{ MAX_COLUMNS } 列までです." if width > MAX_COLUMNS
|
||||||
{ columns: (0...width).map { |index| { key: index.to_s,
|
{ columns: (0...width).map { |index| { key: index.to_s,
|
||||||
label: "#{ ('A'.ord + index).chr }:#{ headers&.[](index).presence || '列' }" } },
|
label: "#{ column_name(index) }:#{ headers&.[](index).presence || '列' }" } },
|
||||||
rows: table.each_with_index.map { |values, index| { source_row: index + (@has_header ? 2 : 1), values: } } }
|
rows: table.each_with_index.map { |values, index| { source_row: index + (@has_header ? 2 : 1), values: } } }
|
||||||
rescue CSV::MalformedCSVError => e
|
rescue CSV::MalformedCSVError => e
|
||||||
raise ArgumentError, "CSV・TSV の形式が不正です: #{ e.message }"
|
raise ArgumentError, "CSV・TSV の形式が不正です: #{ e.message }"
|
||||||
@@ -42,7 +44,8 @@ class PostImportSourceParser
|
|||||||
|
|
||||||
ensure_limits!(value)
|
ensure_limits!(value)
|
||||||
keys = value.grep(Hash).flat_map(&:keys).map(&:to_s).uniq
|
keys = value.grep(Hash).flat_map(&:keys).map(&:to_s).uniq
|
||||||
{ columns: keys.each_with_index.map { |key, index| { key:, label: "#{ ('A'.ord + index).chr }:#{ key }" } },
|
raise ArgumentError, "列数は #{ MAX_COLUMNS } 列までです." if keys.length > MAX_COLUMNS
|
||||||
|
{ columns: keys.each_with_index.map { |key, index| { key:, label: "#{ column_name(index) }:#{ key }" } },
|
||||||
rows: value.each_with_index.map { |item, index| { source_row: index + 1,
|
rows: value.each_with_index.map { |item, index| { source_row: index + 1,
|
||||||
values: keys.map { json_cell(item.is_a?(Hash) ? item[_1] || item[_1.to_sym] : nil) } } } }
|
values: keys.map { json_cell(item.is_a?(Hash) ? item[_1] || item[_1.to_sym] : nil) } } } }
|
||||||
rescue JSON::ParserError, KeyError, ArgumentError => e
|
rescue JSON::ParserError, KeyError, ArgumentError => e
|
||||||
@@ -62,4 +65,14 @@ class PostImportSourceParser
|
|||||||
else raise ArgumentError, 'JSON のセル値が不正です.'
|
else raise ArgumentError, 'JSON のセル値が不正です.'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def column_name index
|
||||||
|
name = +''
|
||||||
|
loop do
|
||||||
|
name.prepend((65 + (index % 26)).chr)
|
||||||
|
index = (index / 26) - 1
|
||||||
|
break if index < 0
|
||||||
|
end
|
||||||
|
name
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
|||||||
url_column: urlColumn,
|
url_column: urlColumn,
|
||||||
mappings,
|
mappings,
|
||||||
existing })
|
existing })
|
||||||
setRows (data.rows)
|
setRows (current => mergeValidatedRows (current, data.rows))
|
||||||
setPhase ('preview')
|
setPhase ('preview')
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -126,6 +126,15 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mergeValidatedRows = (current: ImportRow[], validated: ImportRow[]): ImportRow[] =>
|
||||||
|
current.filter (row => row.importStatus === 'created').concat (validated.map (row => {
|
||||||
|
const previous = current.find (_1 => _1.sourceRow === row.sourceRow)
|
||||||
|
return previous ? { ...row,
|
||||||
|
importStatus: previous.importStatus,
|
||||||
|
createdPostId: previous.createdPostId,
|
||||||
|
errors: previous.importStatus === 'failed' ? previous.errors : row.errors } : row
|
||||||
|
}))
|
||||||
|
|
||||||
const updateAttribute = async (index: number, field: string, value: string) => {
|
const updateAttribute = async (index: number, field: string, value: string) => {
|
||||||
const row = rows[index]
|
const row = rows[index]
|
||||||
const next = { ...row,
|
const next = { ...row,
|
||||||
@@ -138,10 +147,10 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
||||||
{ rows: nextRows, existing,
|
{ rows: nextRows.filter (row => row.importStatus !== 'created'), existing,
|
||||||
changed_row: -1 })
|
changed_row: -1 })
|
||||||
if (generation === validationGeneration.current)
|
if (generation === validationGeneration.current)
|
||||||
setRows (validated.rows)
|
setRows (current => mergeValidatedRows (current, validated.rows))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -164,10 +173,10 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
||||||
{ rows: nextRows, existing,
|
{ rows: nextRows.filter (row => row.importStatus !== 'created'), existing,
|
||||||
changed_row: row.sourceRow })
|
changed_row: row.sourceRow })
|
||||||
if (generation === validationGeneration.current)
|
if (generation === validationGeneration.current)
|
||||||
setRows (validated.rows)
|
setRows (current => mergeValidatedRows (current, validated.rows))
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -187,9 +196,9 @@ const PostImportPage: FC<Props> = ({ user }) => {
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
const validated = await apiPost<{ rows: ImportRow[] }> ('/posts/import/validate',
|
||||||
{ rows, existing: value, changed_row: -1 })
|
{ rows: rows.filter (row => row.importStatus !== 'created'), existing: value, changed_row: -1 })
|
||||||
if (generation === validationGeneration.current)
|
if (generation === validationGeneration.current)
|
||||||
setRows (validated.rows)
|
setRows (current => mergeValidatedRows (current, validated.rows))
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする