From bac452011be45676428786b66df096af001cca7e Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 31 Aug 2023 22:29:17 +0200 Subject: [PATCH] Import modal: better error for Invalid file format. (#4159) (#4163) * Import modal: better error for Invalid file format. we're checking the mime-type before allowing collection upload, this mostly works, but in some cases (see AAP-15541), it can fail unexpectedly. This should make debugging that case easier, by including the detected type in the error message. No-Issue * make filetype detection error skippable (cherry picked from commit 9d4df78ee0d5c82028b70b160caecca61c0e4bc7) --- src/components/import-modal/import-modal.scss | 6 ++- src/components/import-modal/import-modal.tsx | 43 ++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/components/import-modal/import-modal.scss b/src/components/import-modal/import-modal.scss index bce7278f99..1d489f2b75 100644 --- a/src/components/import-modal/import-modal.scss +++ b/src/components/import-modal/import-modal.scss @@ -3,7 +3,11 @@ $green: #5bb75b; .upload-collection { .file-error-messages { - color: #c00; + color: var(--pf-global--danger-color--100); + + &.skipped { + color: var(--pf-global--warning-color--100); + } } .upload-file { diff --git a/src/components/import-modal/import-modal.tsx b/src/components/import-modal/import-modal.tsx index 87c1f0f16b..4ec6d765fa 100644 --- a/src/components/import-modal/import-modal.tsx +++ b/src/components/import-modal/import-modal.tsx @@ -2,7 +2,8 @@ import { t } from '@lingui/macro'; import { Button, Modal, Radio } from '@patternfly/react-core'; import { FolderOpenIcon, SpinnerIcon } from '@patternfly/react-icons'; import axios from 'axios'; -import * as React from 'react'; +import cx from 'classnames'; +import React from 'react'; import { AnsibleRepositoryAPI, AnsibleRepositoryType, @@ -36,6 +37,7 @@ interface IProps { interface IState { file?: File; errors?: string; + errorVariant: 'default' | 'skippable' | 'skipped'; uploadProgress: number; uploadStatus: Status; allRepos: AnsibleRepositoryType[]; @@ -57,6 +59,7 @@ export class ImportModal extends React.Component { this.state = { file: undefined, errors: '', + errorVariant: 'default' as const, uploadProgress: 0, uploadStatus: Status.waiting, allRepos: [], @@ -161,7 +164,13 @@ export class ImportModal extends React.Component { render() { const { isOpen, collection } = this.props; - const { file, errors, uploadProgress, uploadStatus } = this.state; + const { file, errors, errorVariant, uploadProgress, uploadStatus } = + this.state; + const skipError = () => { + if (errorVariant === 'skippable') { + this.setState({ errorVariant: 'skipped' as const }); + } + }; return ( { {errors ? ( - - {errors} + + {errors} + {errorVariant === 'skippable' && ( + <> + {' '} + {t`Upload anyway?`} + + )} ) : null} @@ -261,7 +276,11 @@ export class ImportModal extends React.Component { fixedRepos={this.state.fixedRepos} selectedRepos={this.state.selectedRepos} setSelectedRepos={(repos) => - this.setState({ selectedRepos: repos, errors: '' }) + this.setState({ + selectedRepos: repos, + errors: '', + errorVariant: 'default' as const, + }) } hideFixedRepos={true} loadRepos={(params, setRepositoryList, setLoading, setItemsCount) => @@ -279,7 +298,7 @@ export class ImportModal extends React.Component { } private canUpload() { - if (this.state.errors) { + if (this.state.errors && this.state.errorVariant !== 'skipped') { return false; } @@ -312,16 +331,21 @@ export class ImportModal extends React.Component { if (files.length > 1) { this.setState({ errors: t`Please select no more than one file.`, + errorVariant: 'default' as const, }); } else if (!this.acceptedFileTypes.includes(newCollection.type)) { + const detectedType = newCollection.type || t`unknown`; + const acceptedTypes: string = this.acceptedFileTypes.join(', '); this.setState({ - errors: t`Invalid file format.`, + errors: t`Invalid file format: ${detectedType} (expected: ${acceptedTypes}).`, + errorVariant: 'skippable' as const, file: newCollection, uploadProgress: 0, }); } else if (!this.COLLECTION_NAME_REGEX.test(newCollection.name)) { this.setState({ errors: t`Invalid file name. Collections must be formatted as 'namespace-collection_name-1.0.0'`, + errorVariant: 'default' as const, file: newCollection, uploadProgress: 0, }); @@ -331,18 +355,21 @@ export class ImportModal extends React.Component { ) { this.setState({ errors: t`The collection you have selected doesn't appear to match ${collection.name}`, + errorVariant: 'default' as const, file: newCollection, uploadProgress: 0, }); } else if (this.props.namespace != newCollection.name.split('-')[0]) { this.setState({ errors: t`The collection you have selected does not match this namespace.`, + errorVariant: 'default' as const, file: newCollection, uploadProgress: 0, }); } else { this.setState({ errors: '', + errorVariant: 'default' as const, file: newCollection, uploadProgress: 0, }); @@ -410,6 +437,7 @@ export class ImportModal extends React.Component { this.setState({ uploadStatus: Status.waiting, errors: errorMessage, + errorVariant: 'default' as const, }); }) .finally(() => { @@ -428,6 +456,7 @@ export class ImportModal extends React.Component { { file: undefined, errors: '', + errorVariant: 'default' as const, uploadProgress: 0, uploadStatus: Status.waiting, },