|
|
|
@@ -30,8 +30,10 @@ import {
|
|
|
|
|
clearPostImportSourceDraft,
|
|
|
|
|
generatePostImportSessionId,
|
|
|
|
|
hasThumbnailBaseValue,
|
|
|
|
|
hasErrorMessages,
|
|
|
|
|
isCompletedReviewRow,
|
|
|
|
|
isExistingSkipRow,
|
|
|
|
|
isManualSkipRow,
|
|
|
|
|
isNonRecoverableFailedRow,
|
|
|
|
|
mergeImportResults,
|
|
|
|
|
processableImportRows,
|
|
|
|
@@ -41,7 +43,7 @@ import {
|
|
|
|
|
reviewSummaryCounts,
|
|
|
|
|
retryImportRow,
|
|
|
|
|
loadPostImportSession,
|
|
|
|
|
serialisedPostImportRowsEqual,
|
|
|
|
|
compactMessageRecord,
|
|
|
|
|
savePostImportSession,
|
|
|
|
|
} from '@/lib/postImportSession'
|
|
|
|
|
import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings'
|
|
|
|
@@ -102,15 +104,48 @@ const isRepairRow = (row: PostImportRow): boolean =>
|
|
|
|
|
row.recoverable === true
|
|
|
|
|
&& (row.importStatus === 'failed'
|
|
|
|
|
|| (row.importStatus === 'pending'
|
|
|
|
|
&& Object.keys (row.validationErrors).length > 0))
|
|
|
|
|
&& hasErrorMessages (row.validationErrors)))
|
|
|
|
|
|
|
|
|
|
const DUPLICATE_URL_MESSAGE = 'URL が重複しています.'
|
|
|
|
|
|
|
|
|
|
const rowMetadataPending = (row: PostImportRow): boolean =>
|
|
|
|
|
row.status === 'pending'
|
|
|
|
|
|
|
|
|
|
const rowOperationBusy = (
|
|
|
|
|
row: PostImportRow,
|
|
|
|
|
submitting: boolean,
|
|
|
|
|
loadingRow: number | null,
|
|
|
|
|
): boolean =>
|
|
|
|
|
submitting
|
|
|
|
|
|| loadingRow === row.sourceRow
|
|
|
|
|
|| rowMetadataPending (row)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const rowSkipBusy = (
|
|
|
|
|
row: PostImportRow,
|
|
|
|
|
submitting: boolean,
|
|
|
|
|
loadingRow: number | null,
|
|
|
|
|
): boolean =>
|
|
|
|
|
submitting
|
|
|
|
|
|| loadingRow === row.sourceRow
|
|
|
|
|
|| (rowMetadataPending (row) && !(isManualSkipRow (row)))
|
|
|
|
|
|
|
|
|
|
const shouldFetchMetadata = (row: PostImportRow): boolean =>
|
|
|
|
|
row.status === 'pending'
|
|
|
|
|
&& row.skipReason !== 'manual'
|
|
|
|
|
&& row.importStatus !== 'created'
|
|
|
|
|
&& row.importStatus !== 'skipped'
|
|
|
|
|
&& !(isNonRecoverableFailedRow (row))
|
|
|
|
|
&& row.metadataUrl == null
|
|
|
|
|
|
|
|
|
|
const applyDuplicateUrlErrors = (
|
|
|
|
|
rows: PostImportRow[],
|
|
|
|
|
): PostImportRow[] => {
|
|
|
|
|
const counts = rows.reduce<Record<string, number>> ((result, row) => {
|
|
|
|
|
if (row.url !== '')
|
|
|
|
|
if (row.url !== ''
|
|
|
|
|
&& !(isManualSkipRow (row))
|
|
|
|
|
&& row.importStatus !== 'created'
|
|
|
|
|
&& row.importStatus !== 'skipped')
|
|
|
|
|
result[row.url] = (result[row.url] ?? 0) + 1
|
|
|
|
|
return result
|
|
|
|
|
}, { })
|
|
|
|
@@ -120,27 +155,30 @@ const applyDuplicateUrlErrors = (
|
|
|
|
|
message => message !== DUPLICATE_URL_MESSAGE)
|
|
|
|
|
if ((counts[row.url] ?? 0) < 2)
|
|
|
|
|
{
|
|
|
|
|
const nextValidationErrors = {
|
|
|
|
|
const nextValidationErrors = compactMessageRecord ({
|
|
|
|
|
...row.validationErrors,
|
|
|
|
|
url: urlErrors }
|
|
|
|
|
const hasErrors = Object.values (nextValidationErrors).some (
|
|
|
|
|
messages => messages.length > 0)
|
|
|
|
|
url: urlErrors })
|
|
|
|
|
const hasErrors = hasErrorMessages (nextValidationErrors)
|
|
|
|
|
return urlErrors.length === (row.validationErrors.url ?? []).length
|
|
|
|
|
? row
|
|
|
|
|
: {
|
|
|
|
|
...row,
|
|
|
|
|
validationErrors: nextValidationErrors,
|
|
|
|
|
skipReason:
|
|
|
|
|
row.skipReason === 'manual'
|
|
|
|
|
? 'manual'
|
|
|
|
|
: row.existingPostId != null
|
|
|
|
|
? 'existing'
|
|
|
|
|
: undefined,
|
|
|
|
|
status: hasErrors ? 'error' : row.status }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
skipReason: row.skipReason === 'manual' ? 'manual' : undefined,
|
|
|
|
|
existingPostId: undefined,
|
|
|
|
|
existingPost: undefined,
|
|
|
|
|
validationErrors: {
|
|
|
|
|
validationErrors: compactMessageRecord ({
|
|
|
|
|
...row.validationErrors,
|
|
|
|
|
url: [...new Set ([...urlErrors, DUPLICATE_URL_MESSAGE])] },
|
|
|
|
|
url: [...new Set ([...urlErrors, DUPLICATE_URL_MESSAGE])] }),
|
|
|
|
|
status: 'error' }
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
@@ -179,8 +217,9 @@ const mergeDryRunRow = (
|
|
|
|
|
currentRow: PostImportRow,
|
|
|
|
|
result: PostMetadataResponse,
|
|
|
|
|
): PostImportRow => {
|
|
|
|
|
const fieldWarnings = compactMessageRecord (result.fieldWarnings ?? { })
|
|
|
|
|
const hasWarnings =
|
|
|
|
|
Object.values (result.fieldWarnings ?? { }).some (messages => messages.length > 0)
|
|
|
|
|
Object.values (fieldWarnings).some (messages => messages.length > 0)
|
|
|
|
|
|| (result.baseWarnings?.length ?? 0) > 0
|
|
|
|
|
return applyThumbnailWarning ({
|
|
|
|
|
...currentRow,
|
|
|
|
@@ -195,7 +234,7 @@ const mergeDryRunRow = (
|
|
|
|
|
videoMs: result.videoMs ?? '',
|
|
|
|
|
tags: result.tags ?? '',
|
|
|
|
|
parentPostIds: String (result.parentPostIds ?? currentRow.attributes.parentPostIds ?? '') },
|
|
|
|
|
fieldWarnings: result.fieldWarnings ?? { },
|
|
|
|
|
fieldWarnings,
|
|
|
|
|
baseWarnings: result.baseWarnings ?? [],
|
|
|
|
|
validationErrors: { },
|
|
|
|
|
importErrors: undefined,
|
|
|
|
@@ -291,9 +330,9 @@ const mergePreviewRow = (
|
|
|
|
|
currentRow: PostImportRow,
|
|
|
|
|
preview: PostMetadataResponse,
|
|
|
|
|
): PostImportRow => {
|
|
|
|
|
const fieldWarnings = preview.fieldWarnings ?? { }
|
|
|
|
|
const fieldWarnings = compactMessageRecord (preview.fieldWarnings ?? { })
|
|
|
|
|
const baseWarnings = preview.baseWarnings ?? []
|
|
|
|
|
const validationErrors = preview.validationErrors ?? { }
|
|
|
|
|
const validationErrors = compactMessageRecord (preview.validationErrors ?? { })
|
|
|
|
|
const metadataChanged = currentRow.metadataUrl !== preview.url
|
|
|
|
|
const hasWarnings =
|
|
|
|
|
Object.values (fieldWarnings).some (messages => messages.length > 0)
|
|
|
|
@@ -438,7 +477,20 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
const persistSequenceRef = useRef (0)
|
|
|
|
|
const previewSequenceRef = useRef (0)
|
|
|
|
|
|
|
|
|
|
const persistSession = async (
|
|
|
|
|
const commitSessionState = (
|
|
|
|
|
nextSession: PostImportSession,
|
|
|
|
|
): PostImportSession => {
|
|
|
|
|
const sessionId =
|
|
|
|
|
sessionIdRef.current ?? generatePostImportSessionId ()
|
|
|
|
|
sessionIdRef.current = sessionId
|
|
|
|
|
const persistedSession = buildSession (nextSession.rows)
|
|
|
|
|
savePostImportSession (sessionId, persistedSession)
|
|
|
|
|
sessionRef.current = persistedSession
|
|
|
|
|
setSession (persistedSession)
|
|
|
|
|
return persistedSession
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const syncSessionUrl = async (
|
|
|
|
|
nextSession: PostImportSession,
|
|
|
|
|
): Promise<PostImportSession | null> => {
|
|
|
|
|
const sessionId =
|
|
|
|
@@ -451,26 +503,21 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
if (persistSequenceRef.current !== sequence)
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
const saved = savePostImportSession (sessionId, nextSession)
|
|
|
|
|
const loadedSession = loadPostImportSession (sessionId)
|
|
|
|
|
const canRecoverFromUrlOnly = serialised.complete
|
|
|
|
|
if (!(canRecoverFromUrlOnly)
|
|
|
|
|
&& (!(saved))
|
|
|
|
|
&& !(serialisedPostImportRowsEqual (loadedSession?.rows ?? [], nextSession.rows)))
|
|
|
|
|
return null
|
|
|
|
|
if (saved
|
|
|
|
|
&& !(serialisedPostImportRowsEqual (loadedSession?.rows ?? [], nextSession.rows)))
|
|
|
|
|
return null
|
|
|
|
|
const persistedSession = commitSessionState (nextSession)
|
|
|
|
|
|
|
|
|
|
const persistedSession = buildSession (nextSession.rows)
|
|
|
|
|
const path = appendPostNewSessionId (serialised.path, sessionId)
|
|
|
|
|
persistedSearchRef.current = (new URL (path, window.location.origin)).search
|
|
|
|
|
sessionRef.current = persistedSession
|
|
|
|
|
setSession (persistedSession)
|
|
|
|
|
navigate (path, { replace: true })
|
|
|
|
|
return persistedSession
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentRowBySource = (
|
|
|
|
|
sourceRow: number,
|
|
|
|
|
fallback?: PostImportSession,
|
|
|
|
|
): PostImportRow | null =>
|
|
|
|
|
(sessionRef.current?.rows ?? fallback?.rows ?? []).find (
|
|
|
|
|
row => row.sourceRow === sourceRow) ?? null
|
|
|
|
|
|
|
|
|
|
const finishImport = () => {
|
|
|
|
|
const sessionId = sessionIdRef.current
|
|
|
|
|
if (sessionId != null)
|
|
|
|
@@ -485,7 +532,9 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
const previewSequence = ++previewSequenceRef.current
|
|
|
|
|
const controllers = new Set<AbortController> ()
|
|
|
|
|
|
|
|
|
|
if (sessionRef.current != null && persistedSearchRef.current === location.search)
|
|
|
|
|
if (sessionRef.current != null
|
|
|
|
|
&& persistedSearchRef.current === location.search
|
|
|
|
|
&& !(sessionRef.current.rows.some (row => shouldFetchMetadata (row))))
|
|
|
|
|
return () => {
|
|
|
|
|
previewSequenceRef.current = previewSequence
|
|
|
|
|
}
|
|
|
|
@@ -496,20 +545,16 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
let nextIndex = 0
|
|
|
|
|
const previewErrors = new Map<number, Record<string, string[]>> ()
|
|
|
|
|
let workingRows = [...(sessionRef.current ?? baseSession).rows]
|
|
|
|
|
|
|
|
|
|
const mergeWorkingRow = (
|
|
|
|
|
const mergeCurrentRow = (
|
|
|
|
|
sourceRow: number,
|
|
|
|
|
updater: (row: PostImportRow) => PostImportRow,
|
|
|
|
|
) => {
|
|
|
|
|
workingRows = workingRows.map (row =>
|
|
|
|
|
const latestRows = sessionRef.current?.rows ?? baseSession.rows
|
|
|
|
|
const nextRows = applyDuplicateUrlErrors (latestRows.map (row =>
|
|
|
|
|
row.sourceRow === sourceRow
|
|
|
|
|
? updater (row)
|
|
|
|
|
: row)
|
|
|
|
|
const nextSession = buildSession (workingRows)
|
|
|
|
|
sessionRef.current = nextSession
|
|
|
|
|
setSession (nextSession)
|
|
|
|
|
: row))
|
|
|
|
|
commitSessionState (buildSession (nextRows))
|
|
|
|
|
}
|
|
|
|
|
const worker = async () => {
|
|
|
|
|
while (active)
|
|
|
|
@@ -520,22 +565,25 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
const baseRow = baseSession.rows[index]
|
|
|
|
|
if (baseRow.skipReason === 'manual'
|
|
|
|
|
|| baseRow.importStatus === 'created'
|
|
|
|
|
|| baseRow.importStatus === 'skipped'
|
|
|
|
|
|| isNonRecoverableFailedRow (baseRow)
|
|
|
|
|
|| baseRow.metadataUrl != null)
|
|
|
|
|
const currentRow = currentRowBySource (baseRow.sourceRow, baseSession)
|
|
|
|
|
if (currentRow == null || !(shouldFetchMetadata (currentRow)))
|
|
|
|
|
continue
|
|
|
|
|
const controller = new AbortController ()
|
|
|
|
|
controllers.add (controller)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
const preview = await apiGet<PostMetadataResponse> ('/posts/metadata', {
|
|
|
|
|
params: { url: baseRow.url },
|
|
|
|
|
params: { url: currentRow.url },
|
|
|
|
|
signal: controller.signal })
|
|
|
|
|
if (!(active) || previewSequenceRef.current !== previewSequence)
|
|
|
|
|
return
|
|
|
|
|
mergeWorkingRow (baseRow.sourceRow, row => mergePreviewRow (row, preview))
|
|
|
|
|
mergeCurrentRow (currentRow.sourceRow, row =>
|
|
|
|
|
row.importStatus === 'created'
|
|
|
|
|
|| row.importStatus === 'skipped'
|
|
|
|
|
|| isNonRecoverableFailedRow (row)
|
|
|
|
|
|| row.skipReason === 'manual'
|
|
|
|
|
? row
|
|
|
|
|
: mergePreviewRow (row, preview))
|
|
|
|
|
}
|
|
|
|
|
catch (requestError)
|
|
|
|
|
{
|
|
|
|
@@ -547,21 +595,29 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
errors?: Record<string, string[]>
|
|
|
|
|
baseErrors?: string[]
|
|
|
|
|
}> (requestError)))
|
|
|
|
|
continue
|
|
|
|
|
{
|
|
|
|
|
mergeCurrentRow (currentRow.sourceRow, row => ({
|
|
|
|
|
...row,
|
|
|
|
|
status: 'error' }))
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if (requestError.response?.status === 422)
|
|
|
|
|
{
|
|
|
|
|
const rowErrors = {
|
|
|
|
|
const rowErrors = compactMessageRecord ({
|
|
|
|
|
...(requestError.response.data.errors ?? { }),
|
|
|
|
|
...(requestError.response.data.baseErrors?.length
|
|
|
|
|
? {
|
|
|
|
|
base: requestError.response.data.baseErrors }
|
|
|
|
|
: { }) }
|
|
|
|
|
previewErrors.set (baseRow.sourceRow, rowErrors)
|
|
|
|
|
mergeWorkingRow (baseRow.sourceRow, row => ({
|
|
|
|
|
: { }) })
|
|
|
|
|
mergeCurrentRow (currentRow.sourceRow, row => ({
|
|
|
|
|
...row,
|
|
|
|
|
validationErrors: rowErrors,
|
|
|
|
|
status: 'error' }))
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
mergeCurrentRow (currentRow.sourceRow, row => ({
|
|
|
|
|
...row,
|
|
|
|
|
status: 'error' }))
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
@@ -575,20 +631,13 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
if (!(active) || previewSequenceRef.current !== previewSequence)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
const nextRows = applyDuplicateUrlErrors (workingRows.map (row => {
|
|
|
|
|
const rowErrors = previewErrors.get (row.sourceRow)
|
|
|
|
|
if (rowErrors == null)
|
|
|
|
|
return row
|
|
|
|
|
return {
|
|
|
|
|
...row,
|
|
|
|
|
validationErrors: rowErrors,
|
|
|
|
|
status: 'error' as const }
|
|
|
|
|
}))
|
|
|
|
|
await persistSession (buildSession (nextRows))
|
|
|
|
|
const latestRows = sessionRef.current?.rows ?? baseSession.rows
|
|
|
|
|
const nextRows = applyDuplicateUrlErrors (latestRows)
|
|
|
|
|
await syncSessionUrl (buildSession (nextRows))
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (active)
|
|
|
|
|
if (active && previewSequenceRef.current === previewSequence)
|
|
|
|
|
setMetadataLoading (false)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -649,18 +698,19 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
: rows
|
|
|
|
|
const existingRows = sortedRows.filter (row => isExistingSkipRow (row))
|
|
|
|
|
const reviewRows = sortedRows.filter (row => !(isExistingSkipRow (row)))
|
|
|
|
|
const busy = metadataLoading || submitting || loadingRow != null
|
|
|
|
|
const canSubmit =
|
|
|
|
|
rows.every (row => isCompletedReviewRow (row))
|
|
|
|
|
|| processableImportRows (rows).length > 0
|
|
|
|
|
|
|
|
|
|
const toggleManualSkip = async (sourceRow: number, checked: boolean) => {
|
|
|
|
|
if (busy)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
const currentSession = sessionRef.current
|
|
|
|
|
if (currentSession == null)
|
|
|
|
|
return
|
|
|
|
|
const currentRow = currentRowBySource (sourceRow, currentSession)
|
|
|
|
|
if (currentRow == null)
|
|
|
|
|
return
|
|
|
|
|
if (rowSkipBusy (currentRow, submitting, loadingRow))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
const nextRows = currentSession.rows.map (row => {
|
|
|
|
|
if (row.sourceRow !== sourceRow)
|
|
|
|
@@ -674,7 +724,13 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
existingPostId: checked ? undefined : row.existingPostId,
|
|
|
|
|
existingPost: checked ? undefined : row.existingPost }
|
|
|
|
|
})
|
|
|
|
|
await persistSession (buildSession (nextRows))
|
|
|
|
|
const nextSession = buildSession (applyDuplicateUrlErrors (nextRows))
|
|
|
|
|
if (metadataLoading)
|
|
|
|
|
{
|
|
|
|
|
commitSessionState (nextSession)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
await syncSessionUrl (nextSession)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const saveDraft = async (
|
|
|
|
@@ -687,23 +743,26 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
const currentSession = sessionRef.current
|
|
|
|
|
if (currentSession == null)
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
if (!(canEditReviewRow (row)))
|
|
|
|
|
const currentRow = currentRowBySource (row.sourceRow, currentSession)
|
|
|
|
|
if (currentRow == null)
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
if (!(canEditReviewRow (currentRow)))
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
|
|
|
|
|
const baseRow =
|
|
|
|
|
resetRequested
|
|
|
|
|
? { ...row,
|
|
|
|
|
url: row.resetSnapshot.url,
|
|
|
|
|
attributes: { ...row.resetSnapshot.attributes },
|
|
|
|
|
provenance: { ...row.resetSnapshot.provenance },
|
|
|
|
|
tagSources: { ...row.resetSnapshot.tagSources },
|
|
|
|
|
? { ...currentRow,
|
|
|
|
|
url: currentRow.resetSnapshot.url,
|
|
|
|
|
attributes: { ...currentRow.resetSnapshot.attributes },
|
|
|
|
|
provenance: { ...currentRow.resetSnapshot.provenance },
|
|
|
|
|
tagSources: { ...currentRow.resetSnapshot.tagSources },
|
|
|
|
|
fieldWarnings: Object.fromEntries (
|
|
|
|
|
Object.entries (row.resetSnapshot.fieldWarnings)
|
|
|
|
|
Object.entries (currentRow.resetSnapshot.fieldWarnings)
|
|
|
|
|
.map (([key, values]) => [key, [...values]])),
|
|
|
|
|
baseWarnings: [...row.resetSnapshot.baseWarnings],
|
|
|
|
|
metadataUrl: row.resetSnapshot.metadataUrl,
|
|
|
|
|
baseWarnings: [...currentRow.resetSnapshot.baseWarnings],
|
|
|
|
|
metadataUrl: currentRow.resetSnapshot.metadataUrl,
|
|
|
|
|
thumbnailFile: undefined }
|
|
|
|
|
: row
|
|
|
|
|
: currentRow
|
|
|
|
|
const urlChanged = draft.url !== baseRow.url
|
|
|
|
|
const nextRow = buildNextEditedRow (baseRow, draft, urlChanged)
|
|
|
|
|
let candidateRow = nextRow
|
|
|
|
@@ -724,15 +783,19 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
|
|
|
|
|
const mergedRow = mergeDryRunRow (candidateRow, dryRun)
|
|
|
|
|
const mergedRows = replaceImportRow (latestSession.rows, mergedRow)
|
|
|
|
|
const nextSession = await persistSession (buildSession (mergedRows))
|
|
|
|
|
const mergedRows = applyDuplicateUrlErrors (
|
|
|
|
|
replaceImportRow (latestSession.rows, mergedRow))
|
|
|
|
|
const nextSession =
|
|
|
|
|
metadataLoading
|
|
|
|
|
? commitSessionState (buildSession (mergedRows))
|
|
|
|
|
: await syncSessionUrl (buildSession (mergedRows))
|
|
|
|
|
if (nextSession == null)
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
const mergedTarget = nextSession.rows.find (
|
|
|
|
|
currentRow => currentRow.sourceRow === baseRow.sourceRow)
|
|
|
|
|
if (mergedTarget == null)
|
|
|
|
|
return { saved: false, row: null }
|
|
|
|
|
if (Object.keys (mergedTarget.validationErrors).length > 0)
|
|
|
|
|
if (hasErrorMessages (mergedTarget.validationErrors))
|
|
|
|
|
return { saved: false, row: mergedTarget }
|
|
|
|
|
return { saved: true, row: null }
|
|
|
|
|
}
|
|
|
|
@@ -746,7 +809,12 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
{
|
|
|
|
|
const errorRow = {
|
|
|
|
|
...candidateRow,
|
|
|
|
|
validationErrors: requestError.response.data.errors ?? { },
|
|
|
|
|
validationErrors: compactMessageRecord ({
|
|
|
|
|
...(requestError.response.data.errors ?? { }),
|
|
|
|
|
...(requestError.response.data.baseErrors?.length
|
|
|
|
|
? {
|
|
|
|
|
base: requestError.response.data.baseErrors }
|
|
|
|
|
: { }) }),
|
|
|
|
|
baseWarnings: [],
|
|
|
|
|
fieldWarnings: { },
|
|
|
|
|
status: 'error' as const }
|
|
|
|
@@ -778,39 +846,48 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const editRow = async (row: PostImportRow) => {
|
|
|
|
|
setEditingRow (row)
|
|
|
|
|
const currentRow = sessionRef.current?.rows.find (
|
|
|
|
|
current => current.sourceRow === row.sourceRow)
|
|
|
|
|
if (currentRow == null)
|
|
|
|
|
return
|
|
|
|
|
if (!(canEditReviewRow (currentRow)))
|
|
|
|
|
return
|
|
|
|
|
if (rowOperationBusy (currentRow, submitting, loadingRow))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
setEditingRow (currentRow)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await openEditingDialogue (row)
|
|
|
|
|
await openEditingDialogue (currentRow)
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
setEditingRow (current =>
|
|
|
|
|
current?.sourceRow === row.sourceRow
|
|
|
|
|
current?.sourceRow === currentRow.sourceRow
|
|
|
|
|
? null
|
|
|
|
|
: current)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const retry = async (sourceRow: number) => {
|
|
|
|
|
if (busy)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
const initialSession = sessionRef.current
|
|
|
|
|
if (initialSession == null)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
setLoadingRow (sourceRow)
|
|
|
|
|
const originalRow = initialSession.rows.find (row => row.sourceRow === sourceRow)
|
|
|
|
|
if (originalRow == null)
|
|
|
|
|
{
|
|
|
|
|
setLoadingRow (null)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
if (rowOperationBusy (originalRow, submitting, loadingRow))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
setLoadingRow (sourceRow)
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
const pendingRows = retryImportRow (initialSession.rows, sourceRow)
|
|
|
|
|
const pendingSession = await persistSession (buildSession (pendingRows))
|
|
|
|
|
const pendingSession =
|
|
|
|
|
metadataLoading
|
|
|
|
|
? commitSessionState (buildSession (pendingRows))
|
|
|
|
|
: await syncSessionUrl (buildSession (pendingRows))
|
|
|
|
|
if (pendingSession == null)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
@@ -825,16 +902,20 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
{
|
|
|
|
|
const latest = sessionRef.current ?? pendingSession
|
|
|
|
|
const restoredRows = replaceImportRow (latest.rows, originalRow)
|
|
|
|
|
await persistSession (buildSession (restoredRows))
|
|
|
|
|
if (metadataLoading)
|
|
|
|
|
commitSessionState (buildSession (restoredRows))
|
|
|
|
|
else
|
|
|
|
|
await syncSessionUrl (buildSession (restoredRows))
|
|
|
|
|
toast ({ title: '登録結果が不完全でした' })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const indexedResults = indexedBulkResults ([target], result.results)
|
|
|
|
|
const latestAfterImport = sessionRef.current ?? pendingSession
|
|
|
|
|
const nextRows = mergeImportResults (latestAfterImport.rows, indexedResults)
|
|
|
|
|
const nextRows = applyDuplicateUrlErrors (
|
|
|
|
|
mergeImportResults (latestAfterImport.rows, indexedResults))
|
|
|
|
|
.map (row => applyThumbnailWarning (row))
|
|
|
|
|
const persisted = await persistSession (buildSession (nextRows))
|
|
|
|
|
const persisted = await syncSessionUrl (buildSession (nextRows))
|
|
|
|
|
if (persisted != null
|
|
|
|
|
&& persisted.rows.every (row => isCompletedReviewRow (row)))
|
|
|
|
|
finishImport ()
|
|
|
|
@@ -843,7 +924,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
{
|
|
|
|
|
const latest = sessionRef.current ?? initialSession
|
|
|
|
|
const restoredRows = replaceImportRow (latest.rows, originalRow)
|
|
|
|
|
await persistSession (buildSession (restoredRows))
|
|
|
|
|
if (metadataLoading)
|
|
|
|
|
commitSessionState (buildSession (restoredRows))
|
|
|
|
|
else
|
|
|
|
|
await syncSessionUrl (buildSession (restoredRows))
|
|
|
|
|
toast ({ title: '再試行に失敗しました' })
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
@@ -854,7 +938,7 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
|
|
|
|
|
const submit = async () => {
|
|
|
|
|
const currentSession = sessionRef.current
|
|
|
|
|
if (currentSession == null || busy)
|
|
|
|
|
if (currentSession == null || metadataLoading || submitting || loadingRow != null)
|
|
|
|
|
return
|
|
|
|
|
if (currentSession.rows.every (row => isCompletedReviewRow (row)))
|
|
|
|
|
{
|
|
|
|
@@ -880,9 +964,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
|
|
|
|
|
const indexedResults = indexedBulkResults (processingRows, result.results)
|
|
|
|
|
const latestAfterImport = sessionRef.current ?? buildSession (currentSession.rows)
|
|
|
|
|
const nextRows = mergeImportResults (latestAfterImport.rows, indexedResults)
|
|
|
|
|
const nextRows = applyDuplicateUrlErrors (
|
|
|
|
|
mergeImportResults (latestAfterImport.rows, indexedResults))
|
|
|
|
|
.map (row => applyThumbnailWarning (row))
|
|
|
|
|
const persisted = await persistSession (buildSession (nextRows))
|
|
|
|
|
const persisted = await syncSessionUrl (buildSession (nextRows))
|
|
|
|
|
if (persisted != null
|
|
|
|
|
&& persisted.rows.every (row => isCompletedReviewRow (row)))
|
|
|
|
|
finishImport ()
|
|
|
|
@@ -958,10 +1043,10 @@ const PostImportReviewPage: FC<Props> = ({ user }) => {
|
|
|
|
|
row={row}
|
|
|
|
|
displayNumber={index + 1}
|
|
|
|
|
showSkipToggle={true}
|
|
|
|
|
editDisabled={busy}
|
|
|
|
|
retryDisabled={busy}
|
|
|
|
|
editDisabled={rowOperationBusy (row, submitting, loadingRow)}
|
|
|
|
|
retryDisabled={rowOperationBusy (row, submitting, loadingRow)}
|
|
|
|
|
skipDisabled={
|
|
|
|
|
busy
|
|
|
|
|
rowSkipBusy (row, submitting, loadingRow)
|
|
|
|
|
|| isExistingSkipRow (row)
|
|
|
|
|
|| row.importStatus === 'created'}
|
|
|
|
|
onEdit={() => editRow (row)}
|
|
|
|
|