Skip to content

Commit

Permalink
Merge pull request #1827 from Automattic/add/media-import-improvements
Browse files Browse the repository at this point in the history
Add/media import improvements
  • Loading branch information
aagam-shah authored Jun 5, 2024
2 parents 5024519 + d4784d0 commit 1dc33e7
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 27 deletions.
3 changes: 3 additions & 0 deletions src/bin/vip-import-media.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { MediaImportProgressTracker } from '../lib/media-import/progress';
import { mediaImportCheckStatus } from '../lib/media-import/status';
import { trackEventWithEnv } from '../lib/tracker';

const API_VERSION = 'v2';

const appQuery = `
id,
name,
Expand Down Expand Up @@ -142,6 +144,7 @@ Importing Media into your App...
archiveUrl: url,
overwriteExistingFiles,
importIntermediateImages,
apiVersion: API_VERSION,
},
},
} );
Expand Down
4 changes: 4 additions & 0 deletions src/graphqlTypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,8 @@ export type AppEnvironmentMediaImportStatusFailureDetails = {
__typename?: 'AppEnvironmentMediaImportStatusFailureDetails';
/** List of errors per file */
fileErrors?: Maybe< Array< Maybe< AppEnvironmentMediaImportStatusFailureDetailsFileErrors > > >;
/** URL to download the media import error log */
fileErrorsUrl?: Maybe< Scalars[ 'String' ][ 'output' ] >;
/** List of global errors per import */
globalErrors?: Maybe< Array< Maybe< Scalars[ 'String' ][ 'output' ] > > >;
/** Status of the Media Import prior to failing */
Expand Down Expand Up @@ -944,6 +946,8 @@ export type AppEnvironmentStartDbBackupCopyPayload = {

/** Mutation request input to start a Media Import */
export type AppEnvironmentStartMediaImportInput = {
/** API version to be used for the media import */
apiVersion?: InputMaybe< Scalars[ 'String' ][ 'input' ] >;
/** The unique ID of the Application */
applicationId: Scalars[ 'Int' ][ 'input' ];
/** Publicly accessible URL that contains an archive of the media files to be imported */
Expand Down
1 change: 1 addition & 0 deletions src/lib/media-import/status.generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export type AppQuery = {
__typename?: 'AppEnvironmentMediaImportStatusFailureDetails';
previousStatus?: string | null;
globalErrors?: Array< string | null > | null;
fileErrorsUrl?: string | null;
fileErrors?: Array< {
__typename?: 'AppEnvironmentMediaImportStatusFailureDetailsFileErrors';
fileName?: string | null;
Expand Down
141 changes: 114 additions & 27 deletions src/lib/media-import/status.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApolloClient, NormalizedCacheObject } from '@apollo/client';
import chalk from 'chalk';
import { prompt } from 'enquirer';
import gql from 'graphql-tag';
import { writeFile } from 'node:fs/promises';
import { resolve } from 'node:path';
Expand All @@ -13,7 +14,7 @@ import {
Maybe,
} from '../../graphqlTypes';
import API from '../../lib/api';
import { capitalize, formatEnvironment, formatData, RunningSprite } from '../../lib/cli/format';
import { capitalize, formatData, formatEnvironment, RunningSprite } from '../../lib/cli/format';
import {
AppForMediaImport,
currentUserCanImportForApp,
Expand Down Expand Up @@ -45,6 +46,7 @@ const IMPORT_MEDIA_PROGRESS_QUERY = gql`
fileName
errors
}
fileErrorsUrl
}
}
}
Expand Down Expand Up @@ -271,44 +273,129 @@ ${ maybeExitPrompt }
void checkStatus( IMPORT_MEDIA_PROGRESS_POLL_INTERVAL );
} );

async function exportFailureDetails(
fileErrors: Maybe< AppEnvironmentMediaImportStatusFailureDetailsFileErrors >[]
) {
const formattedData = buildFileErrors( fileErrors, exportFileErrorsToJson );
const errorsFile = `media-import-${ app.name ?? '' }-${ Date.now() }${
exportFileErrorsToJson ? '.json' : '.txt'
}`;
try {
await writeFile( errorsFile, formattedData );
progressTracker.suffix += `${ chalk.yellow(
`⚠️ All errors have been exported to ${ chalk.bold( resolve( errorsFile ) ) }\n`
) }`;
} catch ( writeFileErr ) {
progressTracker.suffix += `${ chalk.red(
`Could not export errors to file\n${ ( writeFileErr as Error ).message }`
) }`;
}
}

async function fetchFailureDetails( fileErrorsUrl: string ) {
progressTracker.suffix += `
=============================================================
Downloading errors details from ${ fileErrorsUrl }
\n`;
progressTracker.print( { clearAfter: true } );
try {
const response = await fetch( fileErrorsUrl );
return ( await response.json() ) as AppEnvironmentMediaImportStatusFailureDetailsFileErrors[];
} catch ( err ) {
progressTracker.suffix += `${ chalk.red(
`Could not download import errors report\n${ ( err as Error ).message }`
) }`;
throw err;
}
}

async function promptFailureDetailsDownload( fileErrorsUrl: string ) {
const failureDetails = await prompt( {
type: 'confirm',
name: 'download',
message:
'Download import errors report now? (Report will be downloadable for up to 7 days from the completion of the import)',
} );

if ( ! failureDetails.download ) {
progressTracker.suffix += `${ chalk.yellow(
`⚠️ An error report file has been generated for this media import. Access it within the next 15 minutes by clicking on the URL below.`
) }`;
progressTracker.suffix += `\n${ chalk.yellow(
`Or, generate a new URL by running the ${ chalk.bgYellow(
'vip import media status'
) } command.`
) } `;
progressTracker.suffix += `\n${ chalk.yellow(
'The report will be downloadable for up to 7 days after the completion of the import or until a new media import is performed.'
) }`;
progressTracker.suffix += `\n\n${ chalk.underline( fileErrorsUrl ) }\n`;
progressTracker.print( { clearAfter: true } );
return;
}

const failureDetailsErrors = await fetchFailureDetails( fileErrorsUrl );
await exportFailureDetails( failureDetailsErrors );
}

function printFileErrorsReportLinkExpiredError( results: AppEnvironmentMediaImportStatus ) {
if (
results.filesTotal &&
results.filesProcessed &&
results.filesTotal !== results.filesProcessed
) {
const errorsFound = results.filesTotal - results.filesProcessed;
progressTracker.suffix += `${ chalk.yellow(
`⚠️ ${ errorsFound } error(s) were found. File import errors report link expired.`
) }`;
}
}

async function printFailureDetails(
fileErrors: Maybe< AppEnvironmentMediaImportStatusFailureDetailsFileErrors >[],
results: AppEnvironmentMediaImportStatus
) {
progressTracker.suffix += `${ chalk.yellow(
`⚠️ ${ fileErrors.length } file import error(s) were found`
) }`;

if ( ( results.filesTotal ?? 0 ) - ( results.filesProcessed ?? 0 ) !== fileErrors.length ) {
progressTracker.suffix += `. ${ chalk.italic.yellow(
'File import errors report size threshold reached.'
) }`;
}
await exportFailureDetails( fileErrors );
}

try {
const results = await getResults();
const results: AppEnvironmentMediaImportStatus = await getResults();
overallStatus = results.status ?? 'unknown';

progressTracker.stopPrinting();

setProgressTrackerSuffix();
progressTracker.print();

const fileErrors = results.failureDetails?.fileErrors ?? [];
if ( fileErrors.length > 0 ) {
progressTracker.suffix += `${ chalk.yellow(
`⚠️ ${ fileErrors.length } file error(s) have been extracted`
) }`;
if ( ( results.filesTotal ?? 0 ) - ( results.filesProcessed ?? 0 ) !== fileErrors.length ) {
progressTracker.suffix += `. ${ chalk.italic.yellow(
'File-errors report size threshold reached.'
) }`;
}
const formattedData = buildFileErrors( fileErrors, exportFileErrorsToJson );
const errorsFile = `media-import-${ app.name ?? '' }-${ Date.now() }${
exportFileErrorsToJson ? '.json' : '.txt'
}`;
try {
await writeFile( errorsFile, formattedData );
progressTracker.suffix += `\n\n${ chalk.yellow(
`All errors have been exported to ${ chalk.bold( resolve( errorsFile ) ) }`
) }\n\n`;
} catch ( writeFileErr ) {
progressTracker.suffix += `\n\n${ chalk.red(
`Could not export errors to file\n${ ( writeFileErr as Error ).message }`
) }\n\n`;
if ( results.failureDetails?.fileErrorsUrl ) {
await promptFailureDetailsDownload(
results.failureDetails.fileErrorsUrl as unknown as string
);
} else {
const fileErrors = results.failureDetails?.fileErrors ?? [];

if ( fileErrors.length > 0 ) {
// Errors were observed and are present in the dto
// Fall back to exporting errors to local file
await printFailureDetails( fileErrors, results );
} else if ( 'ABORTED' !== overallStatus ) {
// Errors are not present in the dto
// And file error details report link is not available
// do not print this message if the import was aborted
printFileErrorsReportLinkExpiredError( results );
}
}

// Print one final time
progressTracker.print( { clearAfter: true } );

process.exit( 0 );
} catch ( importFailed ) {
progressTracker.stopPrinting();
Expand Down

0 comments on commit 1dc33e7

Please sign in to comment.