frontend/app/MasterList/SyndicationTrigger.tsx (489 lines of code) (raw):
import React, { useEffect, useState } from "react";
import {
CircularProgress,
Dialog,
DialogContent,
Grid,
IconButton,
Tooltip,
DialogTitle,
Typography,
TableCell,
TableContainer,
Table,
TableRow,
TableBody,
DialogContentText,
DialogActions,
Button,
Link,
} from "@material-ui/core";
import {
AccessAlarmOutlined,
BackupOutlined,
CheckCircleOutline,
Error,
} from "@material-ui/icons";
import axios from "axios";
import {
SystemNotification,
SystemNotifcationKind,
} from "@guardian/pluto-headers";
import {
createStyles,
Theme,
withStyles,
WithStyles,
} from "@material-ui/core/styles";
import MuiDialogTitle from "@material-ui/core/DialogTitle";
import CloseIcon from "@material-ui/icons/Close";
import {
getDeliverableDailymotion,
getDeliverableMainstream,
} from "../utils/master-api-service";
import { format, parseISO } from "date-fns";
interface SyndicationTriggerProps {
uploadStatus: string | null;
platform: string;
projectId: number;
assetId: bigint;
sendInitiated: () => void;
title: string | null;
link: string | null;
}
interface SyndicationButtonProps {
disabled: boolean;
onClicked: () => void;
link: string | null;
platformName?: string;
}
interface SyndicationIconProps {
uploadStatus: string | null;
title: string | null;
platform: string;
projectId: number;
assetId: bigint;
}
interface SyndicationDialogProps {
openDialog: boolean;
dialogHandler: () => void;
title: string | null;
platform: string;
uploadStatus: string | null;
projectId: number;
assetId: bigint;
}
const styles = (theme: Theme) =>
createStyles({
root: {
margin: 0,
padding: theme.spacing(2),
},
closeButton: {
position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500],
},
});
interface DialogTitleProps extends WithStyles<typeof styles> {
id: string;
children: React.ReactNode;
onClose: () => void;
}
interface ProgressIconProps {
uploadStatus: string | null;
}
interface LogObject {
timestamp: string;
related_gnm_website: number | null;
related_youtube: number | null;
related_daily_motion: number | null;
related_mainstream: number | null;
sender: string;
log_line: string;
}
//constants representing the incoming uploadStatus values, from choices.py
const NOT_UPLOADING = "Not ready";
const WAITING_FOR_START = "Ready for Upload";
const IN_PROGRESS = "Uploading";
const FAILED = "Upload Failed";
const COMPLETE = "Upload Complete";
const SyndicationTriggerButton: React.FC<SyndicationButtonProps> = (props) => {
const [openDialog, setOpenDialog] = useState<boolean>(false);
const closeDialog = () => {
setOpenDialog(false);
};
const platformName = props.platformName ?? "syndication";
return (
<>
<Tooltip
title={
props.disabled
? `You must add ${platformName} details before starting to upload`
: "Send to syndication partner"
}
>
{props.disabled ? (
<IconButton
id="syndication-trigger"
onClick={(event) => {
event.stopPropagation();
setOpenDialog(true);
}}
>
<BackupOutlined />
</IconButton>
) : (
<IconButton onClick={props.onClicked} id="syndication-trigger">
<BackupOutlined />
</IconButton>
)}
</Tooltip>
<Dialog
open={openDialog}
onClose={closeDialog}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This file has already been uploaded
{props.link ? (
<>
{" "}
to{" "}
<Link href={props.link} target="_blank">
{props.link}
</Link>
</>
) : null}
. Sending it again will cause it to be duplicated. Are you really
sure that you want to continue?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={(event) => {
event.stopPropagation();
props.onClicked();
closeDialog();
}}
>
Continue
</Button>
<Button onClick={closeDialog}>Cancel</Button>
</DialogActions>
</Dialog>
</>
);
};
const ProgressIcon: React.FC<ProgressIconProps> = (props) => {
switch (props.uploadStatus) {
case null:
return null;
case NOT_UPLOADING:
return null;
case WAITING_FOR_START:
return (
<Tooltip title="Waiting for start...">
<AccessAlarmOutlined />
</Tooltip>
);
case IN_PROGRESS:
return (
<Tooltip title="Output is ongoing...">
<CircularProgress style={{ width: "20px", height: "20px" }} />
</Tooltip>
);
case FAILED:
return (
<Tooltip title="Output failed">
<Error style={{ color: "red" }} />
</Tooltip>
);
case COMPLETE:
return (
<Tooltip title="Output success">
<CheckCircleOutline style={{ color: "green", height: "19px" }} />
</Tooltip>
);
default:
console.log("Warning, got unknown upload_status ", props.uploadStatus);
return null;
}
};
const SyndicationDialog: React.FC<SyndicationDialogProps> = (props) => {
const [openDialog, setOpenDialog] = useState<boolean>(props.openDialog);
const [logMessages, setLogMessages] = useState<LogObject[]>([]);
const [uploadStatus, setUploadStatus] = useState<string | null>(
props.uploadStatus
);
const getLogData = async (): Promise<LogObject[]> => {
try {
const { status, data } = await axios.get(
`/api/bundle/${props.projectId}/asset/${props.assetId}/${props.platform}/logs?full`
);
if (status === 200) {
return data.logs;
}
throw "Could not load log messages.";
} catch (error) {
console.error(error);
throw error;
}
};
const doRefresh = async () => {
try {
const logData = await getLogData();
setLogMessages(logData);
} catch (error) {
console.error(error);
}
};
const getUploadStatus = async () => {
try {
if (props.platform == "dailymotion") {
const master: DailymotionMaster = await getDeliverableDailymotion(
props.projectId.toString(),
props.assetId.toString()
);
return master.upload_status;
} else if (props.platform == "mainstream") {
const master: MainstreamMaster = await getDeliverableMainstream(
props.projectId.toString(),
props.assetId.toString()
);
return master.upload_status;
}
} catch (error) {
console.error(error);
}
return "Unknown";
};
const doRefreshUploadStatus = async () => {
try {
const uploadStatus = await getUploadStatus();
setUploadStatus(uploadStatus);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
setOpenDialog(props.openDialog);
}, [props.openDialog]);
useEffect(() => {
doRefresh();
setInterval(doRefresh, 10000);
setInterval(doRefreshUploadStatus, 10000);
}, []);
const DialogTitle = withStyles(styles)((props: DialogTitleProps) => {
const { children, classes, onClose, ...other } = props;
return (
<MuiDialogTitle disableTypography className={classes.root} {...other}>
<Typography variant="h6">{children}</Typography>
{onClose ? (
<IconButton
aria-label="close"
className={classes.closeButton}
onClick={onClose}
>
<CloseIcon />
</IconButton>
) : null}
</MuiDialogTitle>
);
});
return (
<Dialog
open={openDialog}
onClose={props.dialogHandler}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="customized-dialog-title" onClose={props.dialogHandler}>
{props.title} /{" "}
{`${props.platform.charAt(0).toUpperCase()}${props.platform.slice(1)}`}
<div style={{ float: "right", marginRight: "60px", marginTop: "4px" }}>
<ProgressIcon uploadStatus={uploadStatus} />
</div>
</DialogTitle>
<DialogContent>
<TableContainer>
<Table>
<TableBody>
{logMessages
? logMessages.map((item, index) => (
<TableRow>
<TableCell>
{format(parseISO(item.timestamp), "d/M/yyyy H:mm")}
</TableCell>
<TableCell>{item.log_line}</TableCell>
</TableRow>
))
: null}
</TableBody>
</Table>
</TableContainer>
</DialogContent>
</Dialog>
);
};
const SyndicationTriggerIcon: React.FC<SyndicationIconProps> = (props) => {
const [openDialog, setOpenDialog] = useState<boolean>(false);
const dialogHandler = () => {
setOpenDialog(false);
};
switch (props.uploadStatus) {
case null:
return null;
case NOT_UPLOADING:
return null;
case WAITING_FOR_START:
return (
<>
<Tooltip title="Waiting for start... - Click to show full log">
<IconButton
onClick={(event) => {
event.stopPropagation();
setOpenDialog(true);
}}
>
<AccessAlarmOutlined />
</IconButton>
</Tooltip>
<SyndicationDialog
openDialog={openDialog}
dialogHandler={dialogHandler}
title={props.title}
platform={props.platform}
uploadStatus={props.uploadStatus}
projectId={props.projectId}
assetId={props.assetId}
/>
</>
);
case IN_PROGRESS:
return (
<>
<Tooltip title="Output is ongoing... - Click to show full log">
<IconButton
onClick={(event) => {
event.stopPropagation();
setOpenDialog(true);
}}
>
<CircularProgress style={{ width: "20px", height: "20px" }} />
</IconButton>
</Tooltip>
<SyndicationDialog
openDialog={openDialog}
dialogHandler={dialogHandler}
title={props.title}
platform={props.platform}
uploadStatus={props.uploadStatus}
projectId={props.projectId}
assetId={props.assetId}
/>
</>
);
case FAILED:
return (
<>
<Tooltip title="Output failed - Click to show full log">
<IconButton
id="output-failed-logs"
onClick={(event) => {
event.stopPropagation();
setOpenDialog(true);
}}
>
<Error style={{ color: "red" }} />
</IconButton>
</Tooltip>
<SyndicationDialog
openDialog={openDialog}
dialogHandler={dialogHandler}
title={props.title}
platform={props.platform}
uploadStatus={props.uploadStatus}
projectId={props.projectId}
assetId={props.assetId}
/>
</>
);
case COMPLETE:
return (
<>
<Tooltip title="Output success - Click to show full log">
<IconButton
id="output-complete-logs"
onClick={(event) => {
event.stopPropagation();
setOpenDialog(true);
}}
>
<CheckCircleOutline style={{ color: "green", height: "19px" }} />
</IconButton>
</Tooltip>
<SyndicationDialog
openDialog={openDialog}
dialogHandler={dialogHandler}
title={props.title}
platform={props.platform}
uploadStatus={props.uploadStatus}
projectId={props.projectId}
assetId={props.assetId}
/>
</>
);
default:
console.log("warning, got unknown upload_status ", props.uploadStatus);
return null;
}
};
const SyndicationTrigger: React.FC<SyndicationTriggerProps> = (props) => {
const triggerUpload = async () => {
try {
let bundleNumber: number = props.projectId;
if (props.projectId == -1) {
bundleNumber = 0;
}
await axios.post(
`/api/bundle/${bundleNumber}/asset/${props.assetId}/${props.platform}/send`
);
console.log("send initiated");
props.sendInitiated();
SystemNotification.open(
SystemNotifcationKind.Success,
`Started ${props.platform} syndication`
);
} catch (err) {
console.error("Could not start syndication: ", err);
SystemNotification.open(
SystemNotifcationKind.Error,
`Could not start ${props.platform} syndication, see browser console for details`
);
}
};
return (
<>
{props.uploadStatus == IN_PROGRESS ||
props.uploadStatus == WAITING_FOR_START ? null : (
<SyndicationTriggerButton
disabled={
props.uploadStatus == COMPLETE ||
(props.title == null && props.uploadStatus == null)
}
onClicked={triggerUpload}
link={props.link}
platformName={props.platform}
/>
)}
<SyndicationTriggerIcon
uploadStatus={props.uploadStatus}
title={props.title}
platform={props.platform}
projectId={props.projectId}
assetId={props.assetId}
/>
</>
);
};
export default SyndicationTrigger;