From bd363948aa5f0e3e47bf799ea2750ed8db8e9512 Mon Sep 17 00:00:00 2001 From: Yahia Date: Tue, 19 Sep 2023 23:46:08 +0300 Subject: [PATCH] feat: Easier flow for uploading files to the visualizer (#288) --- .../src/components/ControlMenu/Controller.ts | 148 ++++++++---------- .../ControlMenu/partials/UploadMenu.tsx | 111 +++++-------- visualization_tool/src/parser/parser.ts | 90 +++++++++++ visualization_tool/src/types/resources.ts | 1 - 4 files changed, 196 insertions(+), 154 deletions(-) create mode 100644 visualization_tool/src/parser/parser.ts diff --git a/visualization_tool/src/components/ControlMenu/Controller.ts b/visualization_tool/src/components/ControlMenu/Controller.ts index 2e64c9d7..9cebf336 100644 --- a/visualization_tool/src/components/ControlMenu/Controller.ts +++ b/visualization_tool/src/components/ControlMenu/Controller.ts @@ -1,91 +1,81 @@ -import { - OutputFile, - Resource, - ResourceType, - availableResourceTypes, -} from '../../types/resources'; +import {Resource, OutputFile} from '../../types/resources'; +import {IAMRole} from '../../types/IAMPolicy'; -import {IAMRole, IMAPolicyField} from '../../types/IAMPolicy'; +import {parseResources, parseIAMRoles} from '../../parser/parser'; -const titleCase = (str: string) => { - return str - .replace('_', ' ') - .split(' ') - .map(word => word.charAt(0).toUpperCase() + word.slice(1)) - .join(' '); +type FileInfo = { + name: string; + projects: string[]; }; -const parseData = (data: OutputFile, fileName: string) => { - const resources = []; - const projectId = data.project_info.projectId; - console.log(Object.entries(data)); - for (const [resourceType, resourceList] of Object.entries(data)) { - console.log(resourceType); - let type = titleCase(resourceType.slice(0, -1)); - if (type === 'Dns Policie') type = 'Dns Policy'; - if ( - resourceList instanceof Array && - availableResourceTypes.includes(type as ResourceType) - ) { - const currentResources = []; - for (const resource of resourceList) { - const resourceData: Record = {}; - resourceData['file'] = fileName; - resourceData['projectId'] = projectId; +const deleteFile = ( + file: FileInfo, + setFiles: React.Dispatch>, + setResources: React.Dispatch>, + setRoles: React.Dispatch>, + setProjects: React.Dispatch> +) => { + setFiles(prevFiles => { + return prevFiles.filter(prevFile => prevFile.name !== file.name); + }); - for (const [key, value] of Object.entries(resource)) { - switch (typeof value) { - case 'string': - // if this attribute is a link, get the last part of the link - if (value.split('/').length > 1) { - resourceData[key] = value.split('/').at(-1) || 'unknown'; - } else { - resourceData[key] = value; - } - break; - case 'number': - resourceData[key] = value; - break; - default: - break; - } - } + setProjects(prevProjects => { + return prevProjects.filter( + prevProject => !file.projects.includes(prevProject) + ); + }); - resourceData['name'] = resourceData['name'] || 'unknown'; - resourceData['status'] = resourceData['status'] || 'READY'; - resourceData['type'] = type; - - currentResources.push(resourceData as Resource); - } + setResources(prevResources => { + return prevResources.filter( + prevResource => prevResource.file !== file.name + ); + }); + setRoles(prevRoles => { + return prevRoles.filter(prevRole => prevRole.file !== file.name); + }); +}; - resources.push(...currentResources); - } - } +const addFile = ( + file: File, + setFiles: React.Dispatch>, + setResources: React.Dispatch>, + setRoles: React.Dispatch>, + setProjects: React.Dispatch>, + setAllowedProjects: React.Dispatch>, + setError: React.Dispatch> +) => { + const reader = new FileReader(); + reader.readAsText(file); + reader.onload = e => { + const result = e.target?.result as string; - return resources; -}; + try { + const data = JSON.parse(result) as OutputFile; + setProjects(prevProjects => [ + ...prevProjects, + data.project_info.projectId, + ]); + setAllowedProjects(prevProjects => [ + ...prevProjects, + data.project_info.projectId, + ]); + const resources = parseResources(data, file.name); + setResources(prevResources => [...prevResources, ...resources]); -const parseIAMData = (data: OutputFile, fileName: string) => { - const roles: IAMRole[] = []; - const projectId = data.project_info.projectId; - const currentRoles = data.iam_policy as IMAPolicyField[]; + const roles = parseIAMRoles(data, file.name); + setRoles(prevRoles => [...prevRoles, ...roles]); - if (roles instanceof Array) { - for (const role of currentRoles) { - roles.push({ - file: fileName, - projectId, - role: `${projectId}__${role.role.split('/')[1]}`, - members: role.members.map(member => { - return { - memberType: member.split(':')[0], - email: member.split(':')[1], - }; - }), - }); + setFiles(prevFiles => [ + ...prevFiles, + {name: file.name, projects: [data.project_info.projectId]}, + ]); + } catch (err) { + console.log(err); + console.log('Invalid file'); + setError('Invalid file'); + return; } - } - - return roles; + }; }; -export {parseData, parseIAMData}; + +export {deleteFile, addFile}; diff --git a/visualization_tool/src/components/ControlMenu/partials/UploadMenu.tsx b/visualization_tool/src/components/ControlMenu/partials/UploadMenu.tsx index 0a2157da..028bba1d 100644 --- a/visualization_tool/src/components/ControlMenu/partials/UploadMenu.tsx +++ b/visualization_tool/src/components/ControlMenu/partials/UploadMenu.tsx @@ -1,10 +1,9 @@ import {useState, useRef} from 'react'; -import AddIcon from '@mui/icons-material/Add'; import CloseIcon from '@mui/icons-material/Close'; -import {Resource, OutputFile} from '../../../types/resources'; +import {Resource} from '../../../types/resources'; import {IAMRole} from '../../../types/IAMPolicy'; -import {parseData, parseIAMData} from '../Controller'; +import {addFile, deleteFile} from '../Controller'; type UploadMenuProps = { setResources: React.Dispatch>; @@ -13,7 +12,7 @@ type UploadMenuProps = { setAllowedProjects: React.Dispatch>; }; -type File = { +type FileInfo = { name: string; projects: string[]; }; @@ -25,7 +24,7 @@ const UploadMenu = ({ setAllowedProjects, }: UploadMenuProps) => { const fileInput = useRef(null); - const [files, setFiles] = useState([]); + const [files, setFiles] = useState([]); const [error, setError] = useState(null); return (
@@ -40,69 +39,48 @@ const UploadMenu = ({ onSubmit={e => { e.preventDefault(); setError(null); - const file = fileInput.current?.files?.[0]; - if (!file) { + if (!fileInput.current?.files) { setError('No file selected'); return; } - // check if file is already uploaded - if (files.find(prevFile => prevFile.name === file.name)) { - setError('File already uploaded'); - return; - } - - const reader = new FileReader(); - reader.readAsText(file); - reader.onload = e => { - const result = e.target?.result as string; - - try { - const data = JSON.parse(result) as OutputFile; - setProjects(prevProjects => [ - ...prevProjects, - data.project_info.projectId, - ]); - setAllowedProjects(prevProjects => [ - ...prevProjects, - data.project_info.projectId, - ]); - const resources = parseData(data, file.name); - setResources((prevResources: Resource[]) => [ - ...prevResources, - ...resources, - ]); - const roles = parseIAMData(data, file.name); - setRoles((prevRoles: IAMRole[]) => [...prevRoles, ...roles]); + // loop throw files + for (let i = 0; i < fileInput.current?.files?.length; i++) { + const file = fileInput.current?.files?.[i]; - setFiles([ - ...files, - {name: file.name, projects: [data.project_info.projectId]}, - ]); - } catch (err) { - setError('Invalid file'); + // check if file is already uploaded + if (files.find(prevFile => prevFile.name === file.name)) { + setError('File already uploaded'); return; } - }; + + addFile( + file, + setFiles, + setResources, + setRoles, + setProjects, + setAllowedProjects, + setError + ); + } }} > { + setError(null); + // trigger submit + fileInput.current?.form?.dispatchEvent( + new Event('submit', {cancelable: true, bubbles: true}) + ); + }} className="add-input" /> - {error &&

{error}

} {files.length > 0 && ( @@ -113,28 +91,13 @@ const UploadMenu = ({