From 10dc7763135817c604f338fb565b854a256ba7d3 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 12 Jul 2026 01:17:45 +0900 Subject: [PATCH] #399 --- backend/app/services/post_import_previewer.rb | 3 +- .../app/services/post_import_source_parser.rb | 11 +++++- frontend/src/pages/posts/PostImportPage.tsx | 37 ++++++++++++------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/backend/app/services/post_import_previewer.rb b/backend/app/services/post_import_previewer.rb index a276b0a..6facb97 100644 --- a/backend/app/services/post_import_previewer.rb +++ b/backend/app/services/post_import_previewer.rb @@ -57,7 +57,8 @@ class PostImportPreviewer attributes['tags'] = merged_tags(tag_sources, provenance['tags']) { source_row: row[:source_row], url: normal_url || url, attributes:, provenance:, tag_sources:, metadata_url: url, - fetch_warnings:, validation_warnings:, warnings: fetch_warnings + validation_warnings, errors:, + fetch_warnings:, validation_warnings:, warnings: fetch_warnings + validation_warnings, + validation_errors: errors, errors:, status: errors.present? ? 'error' : ((fetch_warnings + validation_warnings).present? ? 'warning' : 'ready'), existing: normal_url.present? && Post.exists?(url: normal_url) } end diff --git a/backend/app/services/post_import_source_parser.rb b/backend/app/services/post_import_source_parser.rb index a58621a..7cad66b 100644 --- a/backend/app/services/post_import_source_parser.rb +++ b/backend/app/services/post_import_source_parser.rb @@ -43,11 +43,18 @@ class PostImportSourceParser raise ArgumentError, 'JSON の投稿候補は配列にしてください.' unless value.is_a?(Array) ensure_limits!(value) - keys = value.grep(Hash).flat_map(&:keys).map(&:to_s).uniq + unless value.all?(Hash) + invalid = value.each_with_index.filter_map { |item, index| index + 1 unless item.is_a?(Hash) } + raise ArgumentError, "JSON の投稿候補は object の配列にしてください: 行 #{ invalid.join(', ') }" + end + keys = value.flat_map(&:keys).map(&:to_s).uniq 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, - values: keys.map { json_cell(item.is_a?(Hash) ? item[_1] || item[_1.to_sym] : nil) } } } } + values: keys.map { |key| + value = item.key?(key) ? item[key] : item[key.to_sym] + json_cell(value) + } } } } rescue JSON::ParserError, KeyError, ArgumentError => e raise ArgumentError, "JSON の形式が不正です: #{ e.message }" end diff --git a/frontend/src/pages/posts/PostImportPage.tsx b/frontend/src/pages/posts/PostImportPage.tsx index 2d9e0fb..4d810da 100644 --- a/frontend/src/pages/posts/PostImportPage.tsx +++ b/frontend/src/pages/posts/PostImportPage.tsx @@ -26,7 +26,8 @@ type ImportRow = { url: string attributes: Record warnings: string[] - errors: Record + validationErrors: Record + importErrors?: Record provenance: Record tagSources?: Record<'automatic' | 'mapped' | 'fixed' | 'manual', string> status: 'ready' | 'warning' | 'error' @@ -127,19 +128,24 @@ const PostImportPage: FC = ({ 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 - })) + current.map (previous => { + if (previous.importStatus === 'created') + return previous + const row = validated.find (_1 => _1.sourceRow === previous.sourceRow) + return row ? { ...row, + importStatus: previous.importStatus, + createdPostId: previous.createdPostId, + importErrors: previous.importErrors, + validationErrors: row.validationErrors ?? row.errors ?? { } } : previous + }).concat (validated.filter (row => !current.some (_1 => _1.sourceRow === row.sourceRow))) const updateAttribute = async (index: number, field: string, value: string) => { const row = rows[index] const next = { ...row, attributes: { ...row.attributes, [field]: value }, - provenance: { ...row.provenance, [field]: 'manual' } } + provenance: { ...row.provenance, [field]: 'manual' }, + importStatus: 'pending' as const, + importErrors: undefined } const nextRows = rows.map ((currentRow, rowIndex) => rowIndex === index ? next : currentRow) setRows (nextRows) const generation = ++validationGeneration.current @@ -165,7 +171,8 @@ const PostImportPage: FC = ({ user }) => { const updateURL = async (index: number, value: string) => { const row = rows[index] - const next = { ...row, url: value, provenance: { ...row.provenance, url: 'manual' } } + const next = { ...row, url: value, provenance: { ...row.provenance, url: 'manual' }, + importStatus: 'pending' as const, importErrors: undefined } const nextRows = rows.map ((currentRow, rowIndex) => rowIndex === index ? next : currentRow) setRows (nextRows) const generation = ++validationGeneration.current @@ -226,7 +233,9 @@ const PostImportPage: FC = ({ user }) => { const resultRow = result.rows.find (_1 => _1.sourceRow === row.sourceRow) return resultRow ? { ...row, importStatus: resultRow.status, createdPostId: resultRow.post?.id, - errors: resultRow.errors ?? row.errors } : row + importErrors: resultRow.errors, + importStatus: resultRow.status, + validationErrors: row.validationErrors } : row })) } catch @@ -334,7 +343,7 @@ const PostImportPage: FC = ({ user }) => { const RowCard = ({ row, index, update, updateURL }: { row: ImportRow, index: number, update: (index: number, field: string, value: string) => void, updateURL: (index: number, value: string) => void }) => (

行 {row.sourceRow}・{row.status}

- {row.importStatus === 'created' ?

登録済み。この行は編集・再登録できません。

: <> + {row.importStatus === 'created' ?

登録済み。この行は編集・再登録できません。{row.createdPostId && 投稿を開く}

: <> updateURL (index, value)}/> update (index, 'title', value)}/> update (index, 'thumbnailBase', value)}/> @@ -345,7 +354,9 @@ const RowCard = ({ row, index, update, updateURL }: { row: ImportRow, index: num update (index, 'tags', value)}/> update (index, 'parentPostIds', value)}/> } - +
) const EditableValue = ({ label, value, origin, onCommit }: { label: string, value: string, origin: string | undefined, onCommit: (value: string) => void }) => {