frontend/app/ProjectEntryList/ProjectDeleteDataComponent.tsx (390 lines of code) (raw):
import React, { useEffect, useState } from "react";
import { RouteComponentProps, useHistory, useLocation } from "react-router-dom";
import {
Button,
Checkbox,
Grid,
IconButton,
Paper,
Tooltip,
} from "@material-ui/core";
import {
getProject,
getProjectByVsid,
startDelete,
getBuckets,
getDeleteJob,
getItemsNotDeleted,
} from "./helpers";
import {
SystemNotification,
SystemNotifcationKind,
} from "@guardian/pluto-headers";
import { Helmet } from "react-helmet";
import { useGuardianStyles } from "~/misc/utils";
import { isLoggedIn } from "~/utils/api";
import { PermMedia } from "@material-ui/icons";
declare var deploymentRootPath: string;
interface ProjectDeleteDataComponentStateTypes {
itemid?: string;
}
type ProjectDeleteDataComponentProps = RouteComponentProps<
ProjectDeleteDataComponentStateTypes
>;
const EMPTY_PROJECT: Project = {
commissionId: -1,
created: new Date().toLocaleDateString(),
deep_archive: false,
deletable: false,
id: 0,
productionOffice: "UK",
isObitProject: null,
projectTypeId: 0,
sensitive: false,
status: "New",
title: "",
user: "",
workingGroupId: 0,
confidential: false,
};
const ProjectDeleteDataComponent: React.FC<ProjectDeleteDataComponentProps> = (
props
) => {
const classes = useGuardianStyles();
const history = useHistory();
const { state: projectFromList } = useLocation<Project | undefined>();
const [project, setProject] = useState<Project>(
projectFromList ?? EMPTY_PROJECT
);
const [errorDialog, setErrorDialog] = useState<boolean>(false);
const [isAdmin, setIsAdmin] = useState<boolean>(false);
const [pluto, setPluto] = useState<boolean>(false);
const [file, setFile] = useState<boolean>(true);
const [backups, setBackups] = useState<boolean>(true);
const [pTR, setPTR] = useState<boolean>(true);
const [deliverables, setDeliverables] = useState<boolean>(true);
const [sAN, setSAN] = useState<boolean>(true);
const [matrix, setMatrix] = useState<boolean>(true);
const [s3, setS3] = useState<boolean>(true);
const [buckets, setBuckets] = useState<string[]>([]);
const [bucketBooleans, updateBucketBooleans] = useState<boolean[]>([]);
const [deleteJobStatus, setDeleteJobStatus] = useState<string>("");
const [itemsNotDeleted, setItemsNotDeleted] = useState<ItemsNotDeleted[]>([]);
const [refreshInterval, setRefreshInterval] = useState<any>();
const getDeleteItemData = async () => {
try {
const id = Number(props.match.params.itemid);
const returnedItems = await getItemsNotDeleted(id);
setItemsNotDeleted(returnedItems);
} catch {
console.log("Could not load items that where not deleted.");
}
};
useEffect(() => {
if (projectFromList) {
return;
}
let isMounted = true;
if (props.match.params.itemid !== "new") {
const loadProject = async (): Promise<void> => {
if (!props.match.params.itemid) throw "No project ID to load";
const id = Number(props.match.params.itemid);
try {
const project = isNaN(id)
? await getProjectByVsid(props.match.params.itemid)
: await getProject(id);
if (isMounted) {
setProject(project);
}
} catch (error) {
if (error.message == "Request failed with status code 404") {
setErrorDialog(true);
}
}
};
loadProject();
}
const fetchWhoIsLoggedIn = async () => {
try {
const loggedIn = await isLoggedIn();
setIsAdmin(loggedIn.isAdmin);
} catch {
setIsAdmin(false);
}
};
fetchWhoIsLoggedIn();
const getBucketData = async () => {
try {
const returnedBuckets = await getBuckets();
for (const bucket in returnedBuckets) {
updateBucketBooleans((arr) => [...arr, true]);
}
setBuckets(returnedBuckets);
} catch {
console.log("Could not load buckets.");
}
};
getBucketData();
const getDeleteJobData = async () => {
try {
const id = Number(props.match.params.itemid);
const returnedStatus = await getDeleteJob(id);
setDeleteJobStatus(returnedStatus);
} catch {
console.log("Could not load delete job status.");
}
};
getDeleteJobData();
const interval = setInterval(() => getDeleteJobData(), 10000);
setRefreshInterval(interval);
getDeleteItemData();
return () => {
isMounted = false;
clearInterval(interval);
};
}, []);
const onProjectSubmit = async (
event: React.FormEvent<HTMLFormElement>
): Promise<void> => {
event.preventDefault();
if (project.title) {
try {
await startDelete(
project.id,
pluto,
file,
backups,
pTR,
deliverables,
sAN,
matrix,
s3,
buckets,
bucketBooleans
);
SystemNotification.open(
SystemNotifcationKind.Success,
`Successfully requested to delete data for project "${project.title}"`
);
} catch {
SystemNotification.open(
SystemNotifcationKind.Error,
`Failed to delete data for project "${project.title}"`
);
}
}
};
useEffect(() => {
if (deleteJobStatus == "Finished") {
getDeleteItemData();
clearInterval(refreshInterval);
}
}, [deleteJobStatus]);
return (
<>
{project ? (
<Helmet>
<title>Delete Data for the Project {project.title}</title>
</Helmet>
) : null}
{isAdmin ? (
<>
<Grid container justifyContent="space-between" spacing={3}>
<Grid item>
<h4>Delete Data for the Project {project.title}</h4>
</Grid>
</Grid>
<Paper className={classes.root} elevation={3}>
<form onSubmit={onProjectSubmit}>
<Grid container xs={12} direction="row" spacing={3}>
<Grid item>
Project Record
<Checkbox
checked={pluto}
onChange={() => setPluto(!pluto)}
name="pluto"
/>
</Grid>
<Grid item>
Project File
<Checkbox
checked={file}
onChange={() => setFile(!file)}
name="file"
/>
</Grid>
<Grid item>
Project File Backups
<Checkbox
checked={backups}
onChange={() => setBackups(!backups)}
name="backups"
/>
</Grid>
<Grid item>
Pointer File
<Checkbox
checked={pTR}
onChange={() => setPTR(!pTR)}
name="ptr"
/>
</Grid>
<Grid item>
Deliverables
<Checkbox
checked={deliverables}
onChange={() => setDeliverables(!deliverables)}
name="deliverables"
/>
</Grid>
<Grid item>
Storage Area Network Data and Vidispine Items
<Checkbox
checked={sAN}
onChange={() => setSAN(!sAN)}
name="san"
/>
</Grid>
<Grid item>
Object Matrix Data
<Checkbox
checked={matrix}
onChange={() => setMatrix(!matrix)}
name="matrix"
/>
</Grid>
<Grid item>
Amazon Web Services Simple Storage Service Data
<Checkbox
checked={s3}
onChange={() => setS3(!s3)}
name="s3"
/>
{s3 ? (
<>
<br />
Buckets
<br />
{buckets
? buckets.map((bucket, ix) => {
return (
<>
{bucket}
<Checkbox
checked={bucketBooleans[ix]}
onChange={() => {
let booleansCopy = [...bucketBooleans];
booleansCopy[ix] = !bucketBooleans[ix];
updateBucketBooleans(booleansCopy);
}}
name={bucket}
/>
<br />
</>
);
})
: null}
</>
) : null}
</Grid>
</Grid>
<div>
Please note the following: -
<br />
<br />
1. If you have 'Project Record' enabled it will break deletion
from Vidispine, the Storage Area Network, and the Object Matrix
system.
<br />
<br />
2. Some parts of the Pluto system where not designed to be
tolerant of data removal. Certain undesirable consequences may
occur if you remove data from the system.
<br />
<br />
3. Deletion from the Amazon Web Services Simple Storage Service
does not take into account that other projects may reference
items from this project. If this is used to delete items which
are referenced by other projects, the other projects will not be
able to load the items.
<br />
<br />
4. Due to a limitation of the pluto-storagetier software, which
this software relies on, this software will only attempt to
delete from the one Object Matrix vault that pluto-storagetier
is configured to access.
<br />
<br />
5. Deletion from the Object Matrix system does not take into
account that other projects may reference items from this
project. If this is used to delete items which are referenced by
other projects, the other projects will not be able to load the
items.
<br />
<br />
6. Deletion from Vidispine does not take into account that other
projects which are currently archived may reference files from
this project. If this is used to delete files which are
referenced by other projects, the other projects will not be
able to load the files.
<br />
<br />
7. This software will not delete the database backups for
Vidispine, pluto-core, and pluto-deliverables. Data such as
titles, owners, and file names from this project will remain
present in these backups.
</div>
<div className={classes.formButtons}>
<Button
className="cancel"
variant="outlined"
onClick={() => history.goBack()}
>
Back
</Button>
<Tooltip title="See project's media">
<IconButton
onClick={() =>
window.location.assign(`/vs/project/${project.id}`)
}
>
<PermMedia />
</IconButton>
</Tooltip>
<Button type="submit" variant="contained" color="secondary">
Submit Delete Request
</Button>
</div>
</form>
</Paper>
{deleteJobStatus != "" ? (
<Paper
className={classes.root}
elevation={3}
style={{ marginTop: "40px" }}
>
<Grid container xs={12} direction="row" spacing={3}>
<Grid item xs={12} style={{ fontSize: "1.6em" }}>
Storage Area Network Data Delete Job Outcome
</Grid>
<Grid item xs={12}>
{deleteJobStatus == "Started" ? <>Job running...</> : null}
{deleteJobStatus == "Finished" ? (
<>
Deletion instructions sent to RabbitMQ. Please check there
for progress.
{itemsNotDeleted.length > 0 ? (
<>
<br />
<br />
No attempt to delete the following items was made due
to them being in more than one project:-
<br />
</>
) : null}
{itemsNotDeleted
? itemsNotDeleted.map((vidispine_item) => {
const { id, projectEntry, item } = vidispine_item;
return (
<>
<a href={"/vs/item/" + item} target="_blank">
{item}
</a>
<br />
</>
);
})
: null}
</>
) : null}
</Grid>
</Grid>
</Paper>
) : null}
</>
) : (
<div>You do not have access to this page.</div>
)}
</>
);
};
export default ProjectDeleteDataComponent;