frontend/app/MasterList/MasterList.tsx (422 lines of code) (raw):

import React, { useState, useEffect } from "react"; import { makeStyles, IconButton, TableContainer, Table, TableHead, TableRow, TableCell, TableBody, Chip, Tooltip, } from "@material-ui/core"; import guardianEnabled from "../static/guardian_enabled.png"; import guardianDisabled from "../static/guardian_disabled.png"; import youtubeEnabled from "../static/youtube_enabled.png"; import youtubeDisabled from "../static/youtube_disabled.png"; import dailymotionEnabled from "../static/dailymotion_enabled.jpg"; import dailymotionDisabled from "../static/dailymotion_disabled.jpg"; import mainstreamEnabled from "../static/mainstream_enabled.png"; import mainstreamDisabled from "../static/mainstream_disabled.png"; import EditIcon from "@material-ui/icons/Edit"; import AddIcon from "@material-ui/icons/Add"; import { MasterEnum } from "../utils/constants"; import { getDeliverableGNM, getDeliverableYoutube, getDeliverableDailymotion, getDeliverableMainstream, } from "../utils/master-api-service"; import { SystemNotification, SystemNotifcationKind, } from "@guardian/pluto-headers"; import SyndicationTrigger from "./SyndicationTrigger"; import SyndicationLastLog from "./SyndicationLastLog"; import ViewListIcon from "@material-ui/icons/ViewList"; import { format, parseISO } from "date-fns"; const useStyles = makeStyles({ tableContainer: { padding: "0 1rem", }, masterList: { display: "flex", alignItems: "center", paddingBottom: "1rem", width: "100%", visibility: "visible", "&.loading": { transition: "all 0.2s", visibility: "hidden", }, "& .tags .chip": { margin: "2px", }, "& .no-overflow": { textOverflow: "ellipsis", display: "inline-block", overflow: "hidden", width: "250px", whiteSpace: "nowrap", }, "& img": { display: "flex", width: "30px", height: "30px", }, }, }); const tableHeaderTitles: string[] = [ "Group", "", "Publish date", "ID Link", "Title", "", "", "", "", "", ]; declare var deploymentRootPath: string; interface MasterListProps { deliverable: Deliverable; project_id: number; onSyndicationInitiated: (assetId: bigint) => void | undefined; width: number; height: number; } const MasterList: React.FC<MasterListProps> = (props) => { const classes = useStyles(); const { deliverable, project_id } = props; const projectIdUrl = `${deploymentRootPath}project/${project_id.toString()}/asset/${deliverable.id.toString()}`; const [loading, setLoading] = useState<boolean>(true); const [refreshTimerId, setRefreshTimerId] = useState<number | undefined>( undefined ); const [tooSmall, setTooSmall] = useState<boolean>(false); const [masters, setMasters] = useState<Master[]>([ { group: MasterEnum.Guardian, publication_date: null, title: null, link: null, tags: null, upload_status: null, }, { group: MasterEnum.Youtube, publication_date: null, title: null, link: null, tags: null, upload_status: null, }, { group: MasterEnum.Dailymotion, publication_date: null, title: null, link: null, tags: null, upload_status: null, routename: null, job_id: null, }, { group: MasterEnum.Mainstream, publication_date: null, title: null, link: null, tags: null, upload_status: null, routename: null, job_id: null, }, ]); const displayError = (error: any) => { if (error) { SystemNotification.open(SystemNotifcationKind.Error, error?.toString()); } }; useEffect(() => { console.debug("registering clear handler for refreshtimer"); return () => { if (refreshTimerId) { console.debug("clearing refresh timer ", refreshTimerId); window.clearInterval(refreshTimerId); } }; }, [refreshTimerId]); const startRegularRefresh = () => { const timerId = window.setInterval(loadData, 3000); setRefreshTimerId(timerId); }; const loadData = async () => { let gnmMaster: GuardianMaster; try { gnmMaster = await getDeliverableGNM( project_id.toString(), deliverable.id.toString() ); } catch (error) { displayError(error); } let youtubeMaster: YoutubeMaster; try { youtubeMaster = await getDeliverableYoutube( project_id.toString(), deliverable.id.toString() ); } catch (error) { displayError(error); } let dailymotionMaster: DailymotionMaster; try { dailymotionMaster = await getDeliverableDailymotion( project_id.toString(), deliverable.id.toString() ); } catch (error) { displayError(error); } let mainstreamMaster: MainstreamMaster; try { mainstreamMaster = await getDeliverableMainstream( project_id.toString(), deliverable.id.toString() ); } catch (error) { displayError(error); } const updatedMasters = masters.map((master) => { if (master.group === MasterEnum.Guardian && gnmMaster) { return { group: MasterEnum.Guardian, publication_date: gnmMaster.publication_date, title: gnmMaster.website_title, link: gnmMaster.media_atom_id || "", tags: gnmMaster.tags, upload_status: gnmMaster.upload_status, }; } if (master.group === MasterEnum.Youtube && youtubeMaster) { return { group: MasterEnum.Youtube, publication_date: youtubeMaster.publication_date, title: youtubeMaster.youtube_title, link: youtubeMaster.youtube_id, tags: youtubeMaster.youtube_tags, upload_status: null, }; } if (master.group === MasterEnum.Dailymotion && dailymotionMaster) { return { group: MasterEnum.Dailymotion, publication_date: dailymotionMaster.publication_date, title: dailymotionMaster.daily_motion_title, link: dailymotionMaster.daily_motion_url, tags: dailymotionMaster.daily_motion_tags, upload_status: dailymotionMaster.upload_status, routename: dailymotionMaster.routename, job_id: dailymotionMaster.job_id, }; } if (master.group === MasterEnum.Mainstream && mainstreamMaster) { return { group: MasterEnum.Mainstream, publication_date: "", title: mainstreamMaster.mainstream_title, link: "", tags: mainstreamMaster.mainstream_tags, upload_status: mainstreamMaster.upload_status, routename: mainstreamMaster.routename, job_id: mainstreamMaster.job_id, }; } return master; }); setLoading(false); setMasters(updatedMasters); }; const checkSize = () => { if ( props.deliverable.type == 1 || props.deliverable.type == 2 || props.deliverable.type == 14 || props.deliverable.type == 16 ) { if (props.width != 0) { if (props.width < 1280) { setTooSmall(true); } if (props.height < 720) { setTooSmall(true); } } } }; useEffect(() => { loadData(); checkSize(); }, []); const getTypeImageSource = (master: Master) => { const { group, title } = master; const isEnabled = !!title; switch (group) { case MasterEnum.Guardian: return isEnabled ? guardianEnabled : guardianDisabled; case MasterEnum.Youtube: return isEnabled ? youtubeEnabled : youtubeDisabled; case MasterEnum.Dailymotion: return isEnabled ? dailymotionEnabled : dailymotionDisabled; case MasterEnum.Mainstream: return isEnabled ? mainstreamEnabled : mainstreamDisabled; } }; const getPublicationText = (master: Master): string => { const { title, publication_date } = master; if (title === null && publication_date) { return `Publication FAILED ${format( parseISO(publication_date), "EEE do MMM, HH:mm" )}`; } if (publication_date) { return `Published since ${format( parseISO(publication_date), "EEE do MMM, HH:mm" )}`; } if (title) { return "Not sent yet"; } return ""; }; const getMasterLink = (master: Master): string => { switch (master.group) { case MasterEnum.Guardian: return `${projectIdUrl}/atom`; case MasterEnum.Youtube: return `${projectIdUrl}/youtube`; case MasterEnum.Dailymotion: return `${projectIdUrl}/dailymotion`; case MasterEnum.Mainstream: return `${projectIdUrl}/mainstream`; } return ""; }; const getMediaLink = (master: Master): string => { switch (master.group) { case MasterEnum.Guardian: return `https://video.gutools.co.uk/${master.link}`; case MasterEnum.Youtube: return `https://www.youtube.com/watch?v=${master.link}`; } return master.link || ""; }; return ( <div className={`${classes.masterList}${loading ? " loading" : ""}`}> <TableContainer className={classes.tableContainer}> <Table> <TableHead> <TableRow> {tableHeaderTitles.map((title, index) => ( <TableCell key={index}>{title}</TableCell> ))} </TableRow> </TableHead> <TableBody> {tooSmall ? ( <TableRow> <TableCell align="center" colSpan={10}> Video is too small for syndication at {props.width}x {props.height}! </TableCell> </TableRow> ) : null} {masters.map((master, index) => ( <TableRow key={index}> <TableCell> <img src={getTypeImageSource(master)} alt={`${master.group} image`} /> </TableCell> <TableCell> {`${master.group.charAt(0).toUpperCase()}${master.group.slice( 1 )}`} </TableCell> <TableCell className="publication-text"> {getPublicationText(master)} </TableCell> <TableCell className="link"> {master.link ? ( <a className="no-overflow" href={getMediaLink(master)}> {getMediaLink(master)} </a> ) : ( "" )} </TableCell> <TableCell className="platform"> {master.title ? master.title : ""} </TableCell> <TableCell className="tags"> {(master.tags || []).map((tag, index) => ( <Chip className="chip" key={index} variant="outlined" size="small" label={tag} /> ))} </TableCell> <TableCell> {master.title ? ( <Tooltip title="View/edit syndication information"> <IconButton href={getMasterLink(master)}> <EditIcon /> </IconButton> </Tooltip> ) : ( <Tooltip title="Add syndication information"> <IconButton href={getMasterLink(master)}> <AddIcon /> </IconButton> </Tooltip> )} </TableCell> <TableCell style={{ width: "96px" }}> {master.group != MasterEnum.Guardian && master.group != MasterEnum.Youtube ? ( <SyndicationTrigger uploadStatus={master.upload_status} platform={master.group} projectId={props.project_id} assetId={deliverable.id} sendInitiated={() => startRegularRefresh()} title={master.title} link={master.link} /> ) : null} </TableCell> <TableCell> <SyndicationLastLog uploadStatus={master.upload_status} platform={master.group} projectId={props.project_id} assetId={deliverable.id} /> </TableCell> <TableCell style={{ width: "96px" }}> {master.job_id ? ( <Tooltip title="Upload Log"> <IconButton // This is limited to fifty seven characters because CDS is only writing up to fifty seven character long file names not including the file extension. href={`/cds/logByJobName/${master.job_id.substring( 0, 57 )}`} > <ViewListIcon /> </IconButton> </Tooltip> ) : null} </TableCell> </TableRow> ))} </TableBody> </Table> </TableContainer> </div> ); }; export default MasterList;