frontend/app/CommissionsList/CommissionDeleteDataComponent.tsx (482 lines of code) (raw):

import React, { useEffect, useState } from "react"; import { RouteComponentProps, useHistory, useLocation } from "react-router-dom"; import { Button, Checkbox, Grid, Paper } from "@material-ui/core"; import { getBuckets, getDeleteJob } from "../ProjectEntryList/helpers"; import { SystemNotification, SystemNotifcationKind, } from "@guardian/pluto-headers"; import { Helmet } from "react-helmet"; import { useGuardianStyles } from "~/misc/utils"; import { isLoggedIn } from "~/utils/api"; import { loadCommissionData, startDelete, projectsForCommission, } from "./helpers"; import NotDeleted from "~/CommissionsList/NotDeleted"; declare var deploymentRootPath: string; interface CommissionDeleteDataComponentStateTypes { commissionId?: string; } type CommissionDeleteDataComponentProps = RouteComponentProps< CommissionDeleteDataComponentStateTypes >; const CommissionDeleteDataComponent: React.FC<CommissionDeleteDataComponentProps> = ( props ) => { const classes = useGuardianStyles(); const history = useHistory(); const { state: projectFromList } = useLocation<Project | undefined>(); const [isAdmin, setIsAdmin] = useState<boolean>(false); const [commission, setCommission] = 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 [commissionData, setCommissionData] = useState< CommissionFullRecord | undefined >(undefined); const [lastError, setLastError] = useState<null | string>(null); const [projectList, setProjectList] = useState<Project[] | undefined>( undefined ); const [filterTerms, setFilterTerms] = useState<ProjectFilterTerms>({ match: "W_STARTSWITH", }); const [deleteJobStatuses, setDeleteJobStatuses] = useState<any[]>([]); useEffect(() => { if (projectFromList) { return; } let isMounted = true; const doLoadIn = () => { loadCommissionData(Number(props.match.params.commissionId)) .then((comm) => { setCommissionData(comm); }) .catch((err) => { console.error("Could not load commission: ", err); if (err.hasOwnProperty("response")) { console.log("Error was from a response, ", err.response); switch (err.response.status) { case 404: setLastError("This commission does not exist"); break; case 500: setLastError(`Server error: ${err.response.body}`); break; case 503: case 504: setLastError("Server is not responding"); window.setTimeout(doLoadIn, 1000); //try again in 1s break; default: setLastError(`Server returned ${err.response.status}`); break; } } else { console.error("Could not load in commission data: ", err); setLastError(err.toString()); } }); }; doLoadIn(); let newFilters: ProjectFilterTerms = { match: "W_STARTSWITH", }; newFilters.commissionId = Number(props.match.params.commissionId); setFilterTerms(newFilters); const doLoadProjects = () => { projectsForCommission( Number(props.match.params.commissionId), 0, 1000, filterTerms, "desc", "created" ) .then(([projects, count]) => { setProjectList(projects); setLastError(null); }) .catch((err) => { if (err.hasOwnProperty("response")) { console.error( "Server returned an error loading projects list: ", err.response ); switch (err.response.status) { case 400: setLastError( "Could not load projects, client-side search error" ); break; case 500: console.log("Server said", err.response.body); setLastError( "Could not load projects, server error, see console" ); break; case 503: case 504: setLastError("Server not responding, retrying..."); window.setTimeout(doLoadIn, 1000); break; } } else { console.error("Browser error trying to load projects list: ", err); setLastError("Browser error loading projects list"); } }); }; doLoadProjects(); 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(); return () => { isMounted = false; }; }, []); const onProjectSubmit = async ( event: React.FormEvent<HTMLFormElement> ): Promise<void> => { event.preventDefault(); if (commissionData) { try { await startDelete( commissionData.id, commission, pluto, file, backups, pTR, deliverables, sAN, matrix, s3, buckets, bucketBooleans ); SystemNotification.open( SystemNotifcationKind.Success, `Successfully requested to delete data for commission "${commissionData.title}"` ); } catch { SystemNotification.open( SystemNotifcationKind.Error, `Failed to delete data for commission "${commissionData.title}"` ); } } }; useEffect(() => { const getDeleteJobData = async () => { let deleteJobsArrayTwo: any[][] = []; if (projectList) { const goGetTheData = async () => { let statusString = "Unknown"; const getJobData = async (projectObject: Project) => { try { statusString = await getDeleteJob(projectObject.id); } catch { console.log("Could not load delete job status."); } deleteJobsArrayTwo.push([projectObject.id, statusString]); }; for (let projectObject of projectList) { await getJobData(projectObject); } return; }; await goGetTheData(); } const sortedArray = deleteJobsArrayTwo.sort(function (a, b) { return Number(a[0]) - Number(b[0]); }); setDeleteJobStatuses(sortedArray); }; if (projectList) { getDeleteJobData(); const interval = setInterval(() => getDeleteJobData(), 20000); } }, [projectList]); return ( <> {commissionData ? ( <Helmet> <title>Delete Data for the Commission {commissionData.title}</title> </Helmet> ) : null} {isAdmin ? ( <> {commissionData ? ( <> <Grid container justifyContent="space-between" spacing={3}> <Grid item> <h4>Delete Data for the Commission {commissionData.title}</h4> </Grid> </Grid> <Paper className={classes.root} elevation={3}> <form onSubmit={onProjectSubmit}> <Grid container direction="row" spacing={3}> <Grid item> Commission Record <Checkbox checked={commission} onChange={() => { if (!commission) { setPluto(true); } setCommission(!commission); }} name="commission" /> </Grid> <Grid item> Project Records <Checkbox checked={pluto} onChange={() => setPluto(!pluto)} name="pluto" /> </Grid> <Grid item> Project Files <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 Files <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 ( <div key={ix}> {bucket} <Checkbox checked={bucketBooleans[ix]} onChange={() => { let booleansCopy = [...bucketBooleans]; booleansCopy[ix] = !bucketBooleans[ix]; updateBucketBooleans(booleansCopy); }} name={bucket} /> <br /> </div> ); }) : null} </> ) : null} </Grid> </Grid> <div> Please note the following: - <br /> <br /> 1. If you have 'Project Records' 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 projects in other commissions may reference items from projects in this commission. 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 projects in other commissions may reference items from projects in this commission. 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 commissions which are currently archived may reference files from projects in this commission. 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 commission will remain present in these backups. <br /> <br /> 8. Any attempt to delete the commission record will fail unless the project records are also set to be deleted. </div> <div className={classes.formButtons}> <Button className="cancel" variant="outlined" onClick={() => history.goBack()} > Back </Button> <Button type="submit" variant="contained" color="secondary"> Submit Delete Request </Button> </div> </form> </Paper> {deleteJobStatuses != [] ? deleteJobStatuses.map((statusArray, ix) => { return ( <div key={ix}> {statusArray[1] != "Unknown" ? ( <> {ix == 0 ? ( <Grid container justifyContent="space-between" spacing={3} > <Grid item> <h4> Storage Area Network Data Delete Job Outcomes </h4> </Grid> </Grid> ) : null} <Paper className={classes.root} elevation={3} style={{ marginBottom: "40px" }} > <Grid container direction="row" spacing={3}> <Grid item xs={12} style={{ fontSize: "1.6em" }} > Project {statusArray[0]} </Grid> <Grid item xs={12}> {statusArray[1] == "Started" ? ( <>Job running...</> ) : null} {statusArray[1] == "Finished" ? ( <> Deletion instructions sent to RabbitMQ. <NotDeleted projectId={statusArray[0]} ></NotDeleted> </> ) : null} </Grid> </Grid> </Paper> </> ) : null} </div> ); }) : null} </> ) : ( <div> No commission data for commission{" "} {props.match.params.commissionId} is present in the system. It may have been deleted. </div> )} </> ) : ( <div>You do not have access to this page.</div> )} </> ); }; export default CommissionDeleteDataComponent;