このコミットが含まれているのは:
@@ -92,6 +92,13 @@ const PostImportRowSummary: FC<Props> = ({ row, onEdit }) => {
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{displayStatus != null && <PostImportStatusBadge value={displayStatus}/>}
|
||||
</div>
|
||||
<div className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{String (row.attributes.tags ?? '') || 'タグなし'}
|
||||
</div>
|
||||
<div className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{summaryDate (row)}
|
||||
{row.attributes.duration ? ` / ${ row.attributes.duration }` : ''}
|
||||
</div>
|
||||
{warning && (
|
||||
<div className="text-xs text-amber-700 dark:text-amber-200">
|
||||
{warning}
|
||||
|
||||
@@ -5,6 +5,8 @@ import { creatableImportRows,
|
||||
mergeImportResults,
|
||||
mergeValidatedImportRows,
|
||||
processableImportRows,
|
||||
resultRepairMode,
|
||||
resultRowMessages,
|
||||
resultSummaryCounts,
|
||||
retryImportRow,
|
||||
reviewSummaryCounts } from '@/lib/postImportSession'
|
||||
@@ -131,6 +133,22 @@ describe ('post import row state', () => {
|
||||
importErrors: undefined })
|
||||
})
|
||||
|
||||
it ('deduplicates messages and keeps repair mode only for repairable rows', () => {
|
||||
const repairable = buildPostImportRow ({
|
||||
sourceRow: 1,
|
||||
importStatus: 'pending',
|
||||
validationErrors: { title: ['invalid'], base: ['duplicate'] },
|
||||
importErrors: { base: ['duplicate'], url: ['network'] } })
|
||||
const complete = buildPostImportRow ({
|
||||
sourceRow: 2,
|
||||
importStatus: 'created',
|
||||
createdPostId: 2 })
|
||||
|
||||
expect (resultRowMessages (repairable)).toEqual (['invalid', 'duplicate', 'network'])
|
||||
expect (resultRepairMode ([repairable, complete])).toBe ('failed')
|
||||
expect (resultRepairMode ([complete])).toBe ('all')
|
||||
})
|
||||
|
||||
it ('copies reset snapshot values instead of sharing mutable records', () => {
|
||||
const row = buildPostImportRow ({ fieldWarnings: { title: ['warning'] } })
|
||||
const initialised = initialisePreviewRows ([row])[0]
|
||||
|
||||
@@ -7,6 +7,9 @@ const hasSkipReason = (row: PostImportRow): boolean =>
|
||||
const hasValidationErrors = (row: PostImportRow): boolean =>
|
||||
Object.keys (row.validationErrors ?? { }).length > 0
|
||||
|
||||
const isRepairableImportStatus = (row: PostImportRow): boolean =>
|
||||
row.importStatus === 'failed' || row.importStatus === 'pending'
|
||||
|
||||
const buildResetSnapshot = (row: PostImportRow) => ({
|
||||
url: row.url,
|
||||
attributes: { ...row.attributes },
|
||||
@@ -67,6 +70,20 @@ export const resultSummaryCounts = (rows: PostImportRow[]) =>
|
||||
{ created: 0, skipped: 0, failed: 0 })
|
||||
|
||||
|
||||
export const resultRepairMode = (
|
||||
rows: PostImportRow[],
|
||||
): 'all' | 'failed' =>
|
||||
rows.some (row => hasValidationErrors (row) || isRepairableImportStatus (row))
|
||||
? 'failed'
|
||||
: 'all'
|
||||
|
||||
|
||||
export const resultRowMessages = (row: PostImportRow): string[] =>
|
||||
[...new Set ([
|
||||
...Object.values (row.validationErrors ?? { }).flat (),
|
||||
...Object.values (row.importErrors ?? { }).flat ()])]
|
||||
|
||||
|
||||
export const mergeValidatedImportRows = (
|
||||
current: PostImportRow[],
|
||||
validated: PostImportRow[],
|
||||
|
||||
@@ -20,6 +20,8 @@ import { clearPostImportSourceDraft,
|
||||
loadPostImportSession,
|
||||
mergeImportResults,
|
||||
mergeValidatedImportRows,
|
||||
resultRepairMode,
|
||||
resultRowMessages,
|
||||
resultSummaryCounts,
|
||||
retryImportRow,
|
||||
savePostImportSession } from '@/lib/postImportSession'
|
||||
@@ -34,9 +36,6 @@ import type { User } from '@/types'
|
||||
|
||||
type Props = { user: User | null }
|
||||
|
||||
const rowMessages = (row: PostImportRow): string[] =>
|
||||
Object.values (row.importErrors ?? { }).flat ()
|
||||
|
||||
const buildNextEditedRow = (
|
||||
editingRow: PostImportRow,
|
||||
draft: PostImportRowDraft,
|
||||
@@ -293,7 +292,7 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
|
||||
const resultSession = {
|
||||
...nextSession,
|
||||
rows: nextRows,
|
||||
repairMode: recoverableTarget == null ? 'all' as const : 'failed' as const }
|
||||
repairMode: resultRepairMode (nextRows) }
|
||||
sessionRef.current = resultSession
|
||||
setSession (resultSession)
|
||||
if (recoverableTarget != null
|
||||
@@ -367,10 +366,13 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
|
||||
<div className="space-y-3">
|
||||
{session.rows.map (row => {
|
||||
const displayStatus = displayPostImportStatus (row)
|
||||
const canEdit = row.importStatus === 'failed'
|
||||
const canRetry =
|
||||
const hasValidationErrors = Object.keys (row.validationErrors).length > 0
|
||||
const canEdit =
|
||||
row.importStatus === 'failed'
|
||||
&& Object.keys (row.validationErrors).length === 0
|
||||
|| (row.importStatus === 'pending' && hasValidationErrors)
|
||||
const canRetry =
|
||||
(row.importStatus === 'failed' || row.importStatus === 'pending')
|
||||
&& !(hasValidationErrors)
|
||||
return (
|
||||
<div
|
||||
key={row.sourceRow}
|
||||
@@ -388,7 +390,7 @@ const PostImportResultPage: FC<Props> = ({ user }) => {
|
||||
<div className="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
{row.url}
|
||||
</div>
|
||||
<FieldError messages={rowMessages (row)}/>
|
||||
<FieldError messages={resultRowMessages (row)}/>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
|
||||
新しい課題から参照
ユーザをブロックする