frontend/app/ProjectDeliverables/DeliverableRow.tsx (243 lines of code) (raw):
import React, { useState, useEffect, useCallback, useMemo } from "react";
import {
Collapse,
Grid,
IconButton,
TableCell,
TableRow,
Tooltip,
Typography,
} from "@material-ui/core";
import DeliverableTypeSelector from "../DeliverableTypeSelector";
import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import MasterList from "../MasterList/MasterList";
import { ClassNameMap } from "@material-ui/core/styles/withStyles";
import axios from "axios";
import Cookies from "js-cookie";
import { VidispineItem } from "../vidispine/item/VidispineItem";
import { VError } from "ts-interface-checker";
import DurationFormatter from "./DurationFormatter";
import VidispineJobProgress from "./VidispineJobProgress";
import LaunchIcon from "@material-ui/icons/Launch";
// @ts-ignore
import atomIcon from "../static/atom_icon.svg";
import PriorityHighIcon from "@material-ui/icons/PriorityHigh";
import DeliverableSummaryCell from "./DeliverableSummaryCell";
import DateTimeFormatter from "../Form/DateTimeFormatter";
import ReplayIcon from "@material-ui/icons/Replay";
import { getDeliverable } from "../api-service";
interface DeliverableRowProps {
deliverable: Deliverable;
classes: ClassNameMap<string>;
typeOptions: DeliverableTypes;
parentBundleInfo?: Project;
setCentralMessage: (msg: string) => void;
onCheckedUpdated: (isChecked: boolean) => void;
onNeedsUpdate: () => Promise<void>;
onOnlineLoadError?: (err: string) => void;
vidispineBaseUri: string;
openJob: (jobId: string) => void;
project_id: number;
onSyndicationStarted: (assetId: bigint) => void;
}
const DeliverableRow: React.FC<DeliverableRowProps> = (props) => {
const [open, setOpen] = useState<boolean>(false);
const [version, setVersion] = useState<number | undefined>(undefined);
const [duration, setDuration] = useState<number | undefined>(undefined);
const [deliverable, setDeliverable] = useState<Deliverable>(
props.deliverable
);
const [width, setWidth] = useState<number>(0);
const [height, setHeight] = useState<number>(0);
const updateVidispineItem = async () => {
if (!deliverable.online_item_id) {
console.log(
`Deliverable asset ${deliverable.filename} has no online item id`
);
return;
}
const url = `${props.vidispineBaseUri}/API/item/${deliverable.online_item_id}?content=metadata&field=__version,durationSeconds,originalWidth,originalHeight`;
try {
const response = await axios.get(url);
const item = new VidispineItem(response.data); //throws a VError if the data is not valid
console.log("Got item data ", item);
setVersion(item.getLatestVersion());
const loadedWidth = item.getMetadataString("originalWidth");
if (loadedWidth != undefined) {
setWidth(parseInt(loadedWidth));
}
const loadedHeight = item.getMetadataString("originalHeight");
if (loadedHeight != undefined) {
setHeight(parseInt(loadedHeight));
}
const maybeDuration = item.getMetadataString("durationSeconds");
try {
maybeDuration ? setDuration(parseFloat(maybeDuration)) : undefined;
} catch (err) {
console.error("Vidispine durationSeconds was not a number!: ", err);
}
} catch (err) {
if (err instanceof VError) {
console.error("Vidispine sent an invalid response: ", err);
if (props.onOnlineLoadError)
props.onOnlineLoadError("Vidispine sent an invalid response");
} else {
console.error("Could not load data from Vidispine: ", err);
if (props.onOnlineLoadError)
props.onOnlineLoadError("Could not communicate with Vidispine");
}
}
};
const updateHandler = async () => {
const refreshed_deliverable = await getDeliverable(deliverable.id);
setDeliverable(refreshed_deliverable);
if (["Ready", "Ingested"].includes(deliverable.status_string)) {
clearInterval();
}
};
useEffect(() => {
updateVidispineItem();
}, []);
const updateItemType = async (assetId: bigint, newvalue: number) => {
const url = `/api/bundle/${props.parentBundleInfo?.pluto_core_project_id}/asset/${assetId}/setType`;
try {
props.setCentralMessage("Updating item type...");
await axios.put(
url,
{ type: newvalue },
{
headers: {
"X-CSRFToken": Cookies.get("csrftoken"),
},
}
);
setTimeout(() => {
props.setCentralMessage("Update completed");
props.onNeedsUpdate;
}, 1000);
} catch (error) {
console.error("failed to update type: ", error);
props.setCentralMessage(
`Could not update the type, please contact MultimediaTech`
);
}
};
const doRetry = async () => {
const url = `/deliverables/api/asset/${deliverable.id}/jobretry/${deliverable.job_id}`;
try {
await axios.put(url, null, {
headers: {
"X-CSRFToken": Cookies.get("csrftoken"),
},
});
props.onNeedsUpdate;
props.setCentralMessage("Ingest process started");
} catch (error) {
console.error("Failed to retry job: ", error);
props.setCentralMessage("Failed to start ingest process");
}
};
useEffect(() => {
setDeliverable(props.deliverable);
}, [props.deliverable]);
useEffect(() => {
if (!["Ready"].includes(deliverable.status_string)) {
const timer = setTimeout(updateHandler, 5000);
return () => {
clearTimeout(timer);
};
}
}, [deliverable, version, duration]);
const [rerender, setRerender] = useState(false);
// Updates item after vidispine ingest is complete
const onRecordNeedsUpdate = () => {
console.log("DEBUG: Vidispine ingest complete");
// TODO: Update item version and duration after ingest is complete
};
return (
<React.Fragment>
<TableRow className={props.classes.root}>
<TableCell>
<input
type="checkbox"
onChange={(evt) => {
props.onCheckedUpdated(evt.target.checked);
}}
/>
</TableCell>
<TableCell>
<DeliverableSummaryCell deliverable={deliverable} />
</TableCell>
<TableCell>{version ?? "-"}</TableCell>
<TableCell>{deliverable.size_string ?? "-"}</TableCell>
<TableCell>
{duration ? <DurationFormatter durationSeconds={duration} /> : "-"}
</TableCell>
<TableCell>
<DateTimeFormatter value={deliverable.ingest_complete_dt} />
</TableCell>
<TableCell>
<DeliverableTypeSelector
content={props.typeOptions}
showTip={true}
value={deliverable.type}
onChange={(newvalue) => updateItemType(deliverable.id, newvalue)}
/>
</TableCell>
<TableCell>
<DateTimeFormatter value={deliverable.modified_dt} />
</TableCell>
<TableCell>
{deliverable.job_id ? (
<VidispineJobProgress
jobId={deliverable.job_id}
vidispineBaseUrl={props.vidispineBaseUri}
openJob={props.openJob}
onRecordNeedsUpdate={onRecordNeedsUpdate}
modifiedDateTime={deliverable.modified_dt}
status={deliverable.status_string}
/>
) : null}
</TableCell>
<TableCell>
{deliverable.status_string}
{deliverable.status_string == "Ingest failed" ? (
<Tooltip title="Run the failed ingest process again">
<IconButton
size="small"
onClick={() => {
doRetry();
}}
style={{ marginLeft: "6px" }}
>
<ReplayIcon />
</IconButton>
</Tooltip>
) : null}
</TableCell>
<TableCell>
<Tooltip title="Show syndication">
<IconButton
aria-label="expand row"
size="small"
onClick={() => {
setOpen(!open);
}}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
<TableRow className={props.classes.collapsableTableRow}>
<TableCell className="expandable-cell" colSpan={9}>
<Collapse in={open} timeout="auto" unmountOnExit>
<MasterList
deliverable={deliverable}
project_id={props.project_id}
onSyndicationInitiated={props.onSyndicationStarted}
width={width}
height={height}
/>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
};
export default React.memo(DeliverableRow);