Skip to content

Commit

Permalink
add ability to delete data profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
liberty-rising committed Feb 5, 2024
1 parent 1d9e904 commit 026525b
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 9 deletions.
8 changes: 8 additions & 0 deletions backend/database/data_profile_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,11 @@ def get_dataprofile_by_id(self, data_profile_id: int):
.filter(DataProfile.id == data_profile_id)
.first()
)

def delete_dataprofile(self, data_profile_id: int):
"""Delete a DataProfile."""
self.session.query(DataProfile).filter(
DataProfile.id == data_profile_id
).delete()
self.session.commit()
return True
25 changes: 20 additions & 5 deletions backend/routes/data_profile_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ async def save_data_profile(
column_names_and_types=request.column_names_and_types,
)

print(table_name)
# Create the data profile
new_data_profile = DataProfile(
name=request.name,
Expand Down Expand Up @@ -172,8 +171,6 @@ async def generate_suggested_column_types(
column_names, request.data
)

print(suggested_column_types)

return suggested_column_types


Expand All @@ -188,10 +185,8 @@ async def get_data_profile_table_column_names(
)
if data_profile is None:
raise HTTPException(status_code=404, detail="Data Profile not found")
print("data_profile.table_name", data_profile.table_name)
table_manager = TableManager(session)
column_names = table_manager.get_table_column_names(data_profile.table_name)
print("column_names", column_names)
return column_names


Expand Down Expand Up @@ -277,3 +272,23 @@ async def save_extracted_data(
space_manager.upload_files()

return {"message": "Extracted data saved successfully"}


@data_profile_router.delete("/data-profiles/{data_profile_name}/")
async def delete_data_profile(
data_profile_name: str, current_user: User = Depends(get_current_user)
):
with DatabaseManager() as session:
data_profile_manager = DataProfileManager(session)
data_profile = data_profile_manager.get_dataprofile_by_name_and_org(
data_profile_name, current_user.organization_id
)
if data_profile is None:
raise HTTPException(status_code=404, detail="Data Profile not found")

if data_profile.table_name:
table_manager = TableManager(session)
table_manager.drop_table(data_profile.table_name)

data_profile_manager.delete_dataprofile(data_profile.id)
return {"detail": "Data Profile deleted successfully"}
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@nivo/line": "^0.84.0",
"@nivo/pie": "^0.84.0",
"axios": "^1.6.2",
"copy-to-clipboard": "^3.3.3",
"date-fns": "^2.30.0",
"js-cookie": "^3.0.5",
"primereact": "^10.4.0",
Expand Down
42 changes: 38 additions & 4 deletions frontend/src/pages/upload/DataProfileSelector.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import React from "react";
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import React, { useState } from "react";
import {
FormControl,
IconButton,
InputLabel,
ListItemText,
MenuItem,
Select,
} from "@mui/material";
import Delete from "@mui/icons-material/Delete";

const DataProfileSelector = ({ dataProfiles, dataProfile, setDataProfile }) => {
const DataProfileSelector = ({
dataProfiles,
dataProfile,
setDataProfile,
handleOpenDeleteDialog,
}) => {
const [isOpen, setIsOpen] = useState(false);
const isValidDataProfile = dataProfiles.includes(dataProfile);
const safeDataProfile = isValidDataProfile ? dataProfile : "";

const handleOpen = () => {
setIsOpen(true);
};

const handleClose = () => {
setIsOpen(false);
};

return (
<FormControl fullWidth>
<InputLabel id="data-profile-label">Choose a data profile</InputLabel>
Expand All @@ -13,10 +35,22 @@ const DataProfileSelector = ({ dataProfiles, dataProfile, setDataProfile }) => {
value={safeDataProfile}
label="Choose a data profile"
onChange={(e) => setDataProfile(e.target.value)}
onOpen={handleOpen}
onClose={handleClose}
renderValue={(selected) => <ListItemText primary={selected} />}
>
{dataProfiles.map((profile, index) => (
<MenuItem key={index} value={profile}>
{profile}
<ListItemText primary={profile} />
{isOpen && (
<IconButton
edge="end"
aria-label="delete"
onClick={() => handleOpenDeleteDialog(profile)}
>
<Delete />
</IconButton>
)}
</MenuItem>
))}
</Select>
Expand Down
149 changes: 149 additions & 0 deletions frontend/src/pages/upload/DeleteDataProfileWindow.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React, { useState } from "react";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
IconButton,
TextField,
Typography,
} from "@mui/material";
import CheckIcon from "@mui/icons-material/Check";
import CloseIcon from "@mui/icons-material/Close";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import copy from "copy-to-clipboard";

function DeleteDataProfileWindow({
open,
onClose,
dataProfileToDelete,
onDelete,
}) {
const [isCopied, setIsCopied] = useState(false);
const [isFilled, setIsFilled] = useState(false);
const [confirmDataProfile, setConfirmDataProfile] = useState("");

const handleDelete = () => {
if (confirmDataProfile === dataProfileToDelete) {
setConfirmDataProfile("");
setIsFilled(false);
onDelete();
} else {
alert("The entered name does not match the data profile to be deleted.");
}
};

const handleCopy = () => {
copy(dataProfileToDelete);
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000); // change back to original icon after 2 seconds
};

const handleClose = () => {
setConfirmDataProfile("");
setIsFilled(false);
onClose();
};

return (
<Dialog
open={open}
onClose={handleClose}
sx={{ "& .MuiDialog-paper": { width: "400px" } }}
>
<IconButton
onClick={handleClose}
sx={{ position: "absolute", right: 8, top: 8 }}
>
<CloseIcon />
</IconButton>
<DialogTitle>Delete Data Profile</DialogTitle>
<DialogContent>
<DialogContentText>
Deleting a data profile is permanent and cannot be undone.
<br /> <br />
Enter the data profile name below to confirm.
</DialogContentText>

<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
border: "1px solid darkgrey",
borderRadius: "4px",
my: 2,
backgroundColor: "#f0f0f0",
}}
>
<Box sx={{ py: 2, pl: 2 }}>{dataProfileToDelete}</Box>
<IconButton
onClick={handleCopy}
sx={{
py: 2,
pl: 2,
pr: 2,
backgroundColor: "white",
borderRadius: "0",
height: "100%",
borderLeft: "1px solid darkgrey",
}}
>
{isCopied ? <CheckIcon /> : <ContentCopyIcon />}
</IconButton>
</Box>

<Typography variant="body1" sx={{ fontWeight: "bold", mb: 1 }}>
Data profile name <span style={{ color: "red" }}>*</span>
</Typography>

<TextField
autoFocus
margin="dense"
label={!isFilled ? "Enter the name of the data profile" : null}
type="text"
fullWidth
value={confirmDataProfile}
onChange={(e) => {
setConfirmDataProfile(e.target.value);
setIsFilled(e.target.value !== "");
}}
InputLabelProps={{ shrink: false }}
/>
</DialogContent>
<DialogActions sx={{ pb: 2, pr: 2 }}>
<Button onClick={onClose}>Cancel</Button>
<Button
onClick={handleDelete}
autoFocus
disabled={confirmDataProfile !== dataProfileToDelete}
sx={{
color: "white",
backgroundColor:
confirmDataProfile === dataProfileToDelete
? "#b71c1c"
: "#b71c1c80",
"&:hover": {
backgroundColor:
confirmDataProfile === dataProfileToDelete
? "#7f0000"
: "#7f000080",
},
"&.Mui-disabled": {
color: "white", // keep the text color white when the button is disabled
},
}}
>
Delete
</Button>
</DialogActions>
</Dialog>
);
}

export default DeleteDataProfileWindow;
39 changes: 39 additions & 0 deletions frontend/src/pages/upload/UploadPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
import axios from "axios";
import AlertSnackbar from "./AlertSnackbar";
import CreateDataProfileWindow from "./CreateDataProfileWindow";
import DeleteDataProfileWindow from "./DeleteDataProfileWindow";
import DataProfileSelector from "./DataProfileSelector";
import FileUploader from "./FileUploader";
import PreviewTable from "./PreviewTable";
Expand All @@ -23,6 +24,8 @@ function UploadPage() {
message: "",
severity: "info",
});
const [showDeleteDataProfile, setShowDeleteDataProfile] = useState(false);
const [dataProfileToDelete, setDataProfileToDelete] = useState(null);
const [showCreateDataProfile, setShowCreateDataProfile] = useState(false);
const [columnNames, setColumnNames] = useState([]);
const [previewData, setPreviewData] = useState(null);
Expand Down Expand Up @@ -50,6 +53,35 @@ function UploadPage() {
}
}, [dataProfile]);

const handleOpenDeleteDialog = (dataProfile) => {
setDataProfileToDelete(dataProfile);
setShowDeleteDataProfile(true);
};

const handleCloseDeleteDialog = () => {
setShowDeleteDataProfile(false);
setDataProfileToDelete(null);
};

const handleDeleteDataProfile = async () => {
axios
.delete(`${API_URL}data-profiles/${dataProfileToDelete}/`)
.then(() => {
setDataProfiles((prevDataProfiles) =>
prevDataProfiles.filter((profile) => profile !== dataProfileToDelete),
);
if (dataProfileToDelete === dataProfile) {
setDataProfile(null);
}
setShowDeleteDataProfile(false);
})
.catch((error) => {
console.error("Error deleting data profile:", error);
// handle the error as necessary
});
handleCloseDeleteDialog();
};

const handleCreateDataProfile = (
name,
extractInstructions,
Expand Down Expand Up @@ -161,6 +193,13 @@ function UploadPage() {
dataProfiles={dataProfiles}
dataProfile={dataProfile}
setDataProfile={setDataProfile}
handleOpenDeleteDialog={handleOpenDeleteDialog}
/>
<DeleteDataProfileWindow
open={showDeleteDataProfile}
onClose={handleCloseDeleteDialog}
dataProfileToDelete={dataProfileToDelete}
onDelete={handleDeleteDataProfile}
/>
<Button
onClick={() => setShowCreateDataProfile(true)}
Expand Down

0 comments on commit 026525b

Please sign in to comment.