Skip to content

Commit

Permalink
feat: Easier flow for uploading files to the visualizer (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
yahia3200 authored Sep 19, 2023
1 parent fa26ca1 commit bd36394
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 154 deletions.
148 changes: 69 additions & 79 deletions visualization_tool/src/components/ControlMenu/Controller.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | number> = {};
resourceData['file'] = fileName;
resourceData['projectId'] = projectId;
const deleteFile = (
file: FileInfo,
setFiles: React.Dispatch<React.SetStateAction<FileInfo[]>>,
setResources: React.Dispatch<React.SetStateAction<Resource[]>>,
setRoles: React.Dispatch<React.SetStateAction<IAMRole[]>>,
setProjects: React.Dispatch<React.SetStateAction<string[]>>
) => {
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<React.SetStateAction<FileInfo[]>>,
setResources: React.Dispatch<React.SetStateAction<Resource[]>>,
setRoles: React.Dispatch<React.SetStateAction<IAMRole[]>>,
setProjects: React.Dispatch<React.SetStateAction<string[]>>,
setAllowedProjects: React.Dispatch<React.SetStateAction<string[]>>,
setError: React.Dispatch<React.SetStateAction<string | null>>
) => {
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};
111 changes: 37 additions & 74 deletions visualization_tool/src/components/ControlMenu/partials/UploadMenu.tsx
Original file line number Diff line number Diff line change
@@ -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<React.SetStateAction<Resource[]>>;
Expand All @@ -13,7 +12,7 @@ type UploadMenuProps = {
setAllowedProjects: React.Dispatch<React.SetStateAction<string[]>>;
};

type File = {
type FileInfo = {
name: string;
projects: string[];
};
Expand All @@ -25,7 +24,7 @@ const UploadMenu = ({
setAllowedProjects,
}: UploadMenuProps) => {
const fileInput = useRef<HTMLInputElement>(null);
const [files, setFiles] = useState<File[]>([]);
const [files, setFiles] = useState<FileInfo[]>([]);
const [error, setError] = useState<string | null>(null);
return (
<div className="menu-item">
Expand All @@ -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
);
}
}}
>
<input
type="file"
name=""
id=""
accept=".json"
multiple
ref={fileInput}
onChange={() => {
setError(null);
// trigger submit
fileInput.current?.form?.dispatchEvent(
new Event('submit', {cancelable: true, bubbles: true})
);
}}
className="add-input"
/>
<button type="submit" className="add-button">
<AddIcon
sx={{
fontSize: '2rem',
color: '#4285F4',
}}
/>
</button>
</form>
{error && <p className="error">{error}</p>}
{files.length > 0 && (
Expand All @@ -113,28 +91,13 @@ const UploadMenu = ({
<button
className="remove-button"
onClick={() => {
setFiles(prevFiles => {
return prevFiles.filter(
prevFile => prevFile.name !== file.name
);
});

setProjects(prevProjects => {
return prevProjects.filter(
prevProject => !file.projects.includes(prevProject)
);
});

setResources(prevResources => {
return prevResources.filter(
prevResource => prevResource.file !== file.name
);
});
setRoles(prevRoles => {
return prevRoles.filter(
prevRole => prevRole.file !== file.name
);
});
deleteFile(
file,
setFiles,
setResources,
setRoles,
setProjects
);
}}
>
<CloseIcon
Expand Down
Loading

0 comments on commit bd36394

Please sign in to comment.