import React from 'react'; import { ActionGroup, Button, Checkbox, FileUpload, Form, FormGroup, FormHelperText, HelperText, HelperTextItem, Modal, ModalVariant, Popover, } from '@patternfly/react-core'; import { DropEvent } from '@patternfly/react-core/dist/esm/helpers'; import { HelpIcon } from '@patternfly/react-icons'; import { addNotification } from '@redhat-cloud-services/frontend-components-notifications/redux'; import { useNavigate } from 'react-router-dom'; import { parse } from 'toml'; import { mapOnPremToHosted } from './helpers/onPremToHostedBlueprintMapper'; import { ApiRepositoryRequest, useBulkImportRepositoriesMutation, } from '../../store/contentSourcesApi'; import { useAppDispatch } from '../../store/hooks'; import { BlueprintExportResponse } from '../../store/imageBuilderApi'; import { importCustomRepositories, wizardState } from '../../store/wizardSlice'; import { resolveRelPath } from '../../Utilities/path'; import { mapExportRequestToState } from '../CreateImageWizard/utilities/requestMapper'; interface ImportBlueprintModalProps { setShowImportModal: React.Dispatch>; isOpen: boolean; } export const ImportBlueprintModal: React.FunctionComponent< ImportBlueprintModalProps > = ({ setShowImportModal, isOpen }: ImportBlueprintModalProps) => { const onImportClose = () => { setShowImportModal(false); setFilename(''); setFileContent(''); setIsOnPrem(false); setIsRejected(false); setIsInvalidFormat(false); }; const [fileContent, setFileContent] = React.useState(''); const [importedBlueprint, setImportedBlueprint] = React.useState(); const [isInvalidFormat, setIsInvalidFormat] = React.useState(false); const [filename, setFilename] = React.useState(''); const [isLoading, setIsLoading] = React.useState(false); const [isRejected, setIsRejected] = React.useState(false); const [isOnPrem, setIsOnPrem] = React.useState(false); const [isCheckedImportRepos, setIsCheckedImportRepos] = React.useState(true); const dispatch = useAppDispatch(); const [importRepositories] = useBulkImportRepositoriesMutation(); const handleFileInputChange = ( _event: React.ChangeEvent | React.DragEvent, file: File ) => { setFileContent(''); setFilename(file.name); }; async function handleRepositoryImport( blueprintExportedResponse: BlueprintExportResponse ) { if (isCheckedImportRepos && blueprintExportedResponse.content_sources) { const customRepositories: ApiRepositoryRequest[] = blueprintExportedResponse.content_sources.map( (item) => item as ApiRepositoryRequest ); try { const result = await importRepositories({ body: customRepositories, }).unwrap(); dispatch( importCustomRepositories( blueprintExportedResponse.customizations.custom_repositories || [] ) ); if (Array.isArray(result)) { const importedRepositoryNames: string[] = []; result.forEach((repository) => { if (repository.warnings?.length === 0 && repository.url) { importedRepositoryNames.push(repository.url); return; } dispatch( addNotification({ variant: 'warning', title: 'Failed to import custom repositories', description: JSON.stringify(repository.warnings), }) ); }); if (importedRepositoryNames.length !== 0) { dispatch( addNotification({ variant: 'info', title: 'Successfully imported custom repositories', description: importedRepositoryNames.join(', '), }) ); } } } catch { dispatch( addNotification({ variant: 'danger', title: 'Custom repositories import failed', }) ); } } } React.useEffect(() => { if (filename && fileContent) { try { const isToml = filename.endsWith('.toml'); const isJson = filename.endsWith('.json'); if (isToml) { const tomlBlueprint = parse(fileContent); const blueprintFromFile = mapOnPremToHosted(tomlBlueprint); const importBlueprintState = mapExportRequestToState( blueprintFromFile, [] ); setIsOnPrem(true); setImportedBlueprint(importBlueprintState); } else if (isJson) { const blueprintFromFile = JSON.parse(fileContent); try { const blueprintExportedResponse: BlueprintExportResponse = { name: blueprintFromFile.name, description: blueprintFromFile.description, distribution: blueprintFromFile.distribution, customizations: blueprintFromFile.customizations, metadata: blueprintFromFile.metadata, content_sources: blueprintFromFile.content_sources, }; const importBlueprintState = mapExportRequestToState( blueprintExportedResponse, blueprintFromFile.image_requests || [] ); if (blueprintExportedResponse.content_sources) { handleRepositoryImport(blueprintExportedResponse); } setIsOnPrem(false); setImportedBlueprint(importBlueprintState); } catch { const blueprintFromFileMapped = mapOnPremToHosted(blueprintFromFile); const importBlueprintState = mapExportRequestToState( blueprintFromFileMapped, [] ); setIsOnPrem(true); setImportedBlueprint(importBlueprintState); } } } catch (error) { setIsInvalidFormat(true); dispatch( addNotification({ variant: 'warning', title: 'File is not a valid blueprint', description: error?.data?.error?.message, }) ); } } }, [filename, fileContent]); const handleClear = () => { setFilename(''); setFileContent(''); setIsOnPrem(false); setIsRejected(false); setIsInvalidFormat(false); }; const handleDataChange = (_: DropEvent, value: string) => { setFileContent(value); }; const handleFileRejected = () => { setIsRejected(true); setIsOnPrem(false); setFileContent(''); setFilename(''); }; const handleFileReadStarted = () => { setIsLoading(true); }; const handleFileReadFinished = () => { setIsLoading(false); }; const navigate = useNavigate(); const variantSwitch = () => { switch (true) { case isRejected || isInvalidFormat: return 'error'; case isOnPrem: return 'warning'; default: return 'default'; } }; return ( Import pipeline You can import the blueprints you created by using the Red Hat image builder into Insights images to create customized images. } > } onClose={onImportClose} >
setIsCheckedImportRepos((prev) => !prev)} aria-label="Import Custom Repositories checkbox" id="checkbox-import-custom-repositories" name="Import Repositories" data-testid="checkbox-import-custom-repositories" /> {isRejected ? 'Must be a valid Blueprint JSON/TOML file no larger than 512 KB' : isInvalidFormat ? 'Not compatible with the blueprints format.' : isOnPrem ? 'Importing on-premises blueprints is currently in beta. Results may vary.' : 'Upload your blueprint file. Supported formats: JSON, TOML.'}
); };