app/JobPage.jsx (337 lines of code) (raw):
import React, {Component} from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useParams,
withRouter
} from "react-router-dom";
import PropTypes from 'prop-types';
import moment from 'moment';
import StatusFormatter from './StatusFormatter.jsx';
import TypeFormatter from './TypeFormatter.jsx';
import PriorityFormatter from './PriorityFormatter.jsx';
import StepInfoBox from './StepInfoBox.jsx';
import DataInfoBox from './DataInfoBox.jsx';
import SyntaxHighlighter from 'react-syntax-highlighter';
import { gradientDark } from 'react-syntax-highlighter/dist/esm/styles/hljs';
class JobPage extends Component {
static propTypes = {
vidispine_host: PropTypes.string.isRequired,
};
constructor(props){
super(props);
this.state = {
vidispineData: {
data: [],
log: [
{task: []}
]
},
networkAccessError: false,
error401: false,
error500: false,
}
}
setStatePromise(newState) {
return new Promise((resolve,reject)=>this.setState(newState, ()=>resolve()));
}
async getJobData(endpoint) {
try {
const headers = new Headers();
const url = this.props.vidispine_host + "/API/" + endpoint;
await this.setStatePromise({loading: true});
const result = await fetch(url, {headers: {Accept: "application/json", Authorization: "Bearer " + window.localStorage["pluto:access-token"]}});
switch(result.status) {
case 200:
const returnedData = await result.json();
return this.setStatePromise({loading: false, vidispineData: returnedData});
case 401:
return this.setStatePromise({loading: false, error401: true});
case 500:
return this.setStatePromise({loading: false, error500: true});
default:
const errorContent = await result.text();
return this.setStatePromise({loading: false, lastError: errorContent});
}
} catch {
this.setState({
networkAccessError: true
});
}
}
getDataForRefresh = () => {
this.getJobData('job/' + this.props.match.params.id + '?metadata=true');
}
componentDidMount() {
const idToLoad = this.props.match.params.id;
this.getJobData('job/' + idToLoad + '?metadata=true');
setInterval(this.getDataForRefresh, 5000);
}
getValue(data,findthis) {
var returnNow = 0;
for (let [key, value] of Object.entries(data)) {
for (let [key3, value3] of Object.entries(value)) {
if (returnNow == 1) {
return value3;
}
if (findthis == value3) {
returnNow = 1;
}
}
}
return 'Unknown';
}
abort = () => {
const urlAbort = this.props.vidispine_host + "/API/job/" + this.props.match.params.id;
fetch(urlAbort, {headers: {Accept: "application/json", Authorization: "Bearer " + window.localStorage["pluto:access-token"]}, method: 'DELETE'});
this.getJobData('job/' + this.props.match.params.id + '?metadata=true');
}
displayAbort(status) {
if ((status != 'ABORTED') && (status != 'ABORTED_PENDING') && (status != 'FINISHED_WARNING') && (status != 'FINISHED') && (status != 'FAILED_TOTAL')) {
return <div class="abort_button" onClick={this.abort}>Abort</div>
} else {
return <div></div>
}
}
displayProgressBar(current, total) {
if (current < 1) {
return (
<div class="progress_bar_job_page" style={{width:'0%'}}></div>
)
}
const percentNumber = 100 / total;
var percentageDone = Math.round(percentNumber * current);
if (percentageDone > 100) {
percentageDone = 100;
}
return (
<div class="progress_bar_job_page" style={{width:percentageDone+'%'}}></div>
)
}
returnStatusForCSS(status) {
if (status == 'FAILED_TOTAL') {
return "job_data_box_failed";
}
if (status == 'FINISHED') {
return "job_data_box_finished";
}
if (status == 'FINISHED_WARNING') {
return "job_data_box_warning";
}
if (status == 'ABORTED') {
return "job_data_box_aborted";
}
return "job_data_box_middle";
}
displayTime(input) {
if (input == 'Unknown') {
return input;
} else {
var d = Number(parseInt(input));
var h = Math.floor(d / 3600);
var m = Math.floor(d % 3600 / 60);
var s = Math.floor(d % 3600 % 60);
return h + ":" + ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
}
}
handleReverseArray() {
if (this.state.vidispineData.log.task && this.state.vidispineData.log.task.length > 0) {
const reversedArray = this.state.vidispineData.log.task.slice().reverse();
return reversedArray.map((item, i) =><StepInfoBox mapPlace={i} stepData={item} />)
} else {
return
}
}
returnSourceFile() {
const possibleURI = this.getValue(this.state.vidispineData.data, "sourceUri");
if (possibleURI == 'Unknown') {
const filePathMap = this.getValue(this.state.vidispineData.data, "filePathMap");
if (filePathMap != 'Unknown') {
const lastPart = filePathMap.slice(filePathMap.lastIndexOf(',') + 1);
const lastPartOfLastPart = lastPart.slice(lastPart.lastIndexOf('=') + 1);
return lastPartOfLastPart;
}
return 'Unknown';
}
return possibleURI;
}
handleArrayData() {
if (this.state.vidispineData.data && this.state.vidispineData.data.length > 0) {
return this.state.vidispineData.data.slice().map((item, i) =><DataInfoBox mapPlace={i} dataData={item} />)
} else {
return
}
}
render() {
const id = this.props.match.params.id;
const fileName = this.getValue(this.state.vidispineData.data, "originalFilename");
const stepNumber = this.state.vidispineData.hasOwnProperty("currentStep") ? this.state.vidispineData.currentStep.number : 0;
const itemId = this.getValue(this.state.vidispineData.data, "itemId");
const tags = this.getValue(this.state.vidispineData.data, "tags");
const timeLeft = this.getValue(this.state.vidispineData.data, "transcodeEstimatedTimeLeft");
const transcoder = this.getValue(this.state.vidispineData.data, "transcoder");
document.title = "Job " + id + " for " + fileName + " - Vidispine Job Tool";
return (
<div>
<div class="job_page_grid">
<div class="job_page_title_box">
{this.state.error401
? <div class="job_page_error">Permission denied by server. Maybe your login has expired? Click <a href="../../">here</a> to log in again.</div>
: ( this.state.error500
? <div class="job_page_error">Server is not responding correctly. Please inform <a href="mailto:multimediatech@theguardian.com">multimediatech@theguardian.com</a></div>
: ( this.state.networkAccessError
? <div class="job_page_error">Could not connect to the server. Maybe your login has expired? Click <a href="../../">here</a> to log in again.</div>
: <div class="job_page_title">Job {id} for {fileName}</div>
)
)
}
{this.displayAbort(this.state.vidispineData.status)}
</div>
<div class="job_data_box">
<div class="job_data_label">
Progress:
</div>
<div class="job_data_progress_bar">
<div class="progress_bar_background_job_page">
{this.displayProgressBar(stepNumber, this.state.vidispineData.totalSteps)}
</div>
</div>
</div>
<div class="job_data_box_left">
<div class="job_data_label">
Id.:
</div>
<div class="job_data_value">
{id}
</div>
</div>
<div class="job_data_box_middle">
<div class="job_data_label">
Started:
</div>
<div class="job_data_value">
{moment(this.state.vidispineData.started).format("D/M/YYYY H:mm")}
</div>
</div>
<div class="job_data_box_right">
<div class="job_data_label">
User:
</div>
<div class="job_data_value">
{this.state.vidispineData.user}
</div>
</div>
<div class="job_data_box_left">
<div class="job_data_label">
Type:
</div>
<div class="job_data_value">
{<TypeFormatter type={this.state.vidispineData.type}/>}
</div>
</div>
<div class={this.returnStatusForCSS(this.state.vidispineData.status)}>
<div class="job_data_label">
Status:
</div>
<div class="job_data_value">
{<StatusFormatter status={this.state.vidispineData.status}/>}
</div>
</div>
<div class="job_data_box_right">
<div class="job_data_label">
Target Object Id.:
</div>
<a href={"/vs/item/" + itemId} target="_blank">
<div class="job_data_value">
{itemId}
</div>
</a>
</div>
<div class="job_data_box">
<div class="job_data_label">
Original Filename:
</div>
<div class="job_data_value">
{fileName}
</div>
</div>
<div class="job_data_box">
<div class="job_data_label">
Source File:
</div>
<div class="job_data_value">
{this.returnSourceFile()}
</div>
</div>
<div class="job_data_box">
<div class="job_data_label">
Tags:
</div>
<div class="job_data_value">
{tags}
</div>
</div>
<div class="job_data_box_left">
<div class="job_data_label">
Estimated Time Left:
</div>
<div class="job_data_value">
{this.displayTime(timeLeft)}
</div>
</div>
<div class="job_data_box_middle">
<div class="job_data_label">
Job Steps:
</div>
<div class="job_data_value">
{stepNumber} of {this.state.vidispineData.totalSteps}
</div>
</div>
<div class="job_data_box_right">
<div class="job_data_label">
Priority:
</div>
<div class="job_data_value">
{<PriorityFormatter priority={this.state.vidispineData.priority}/>}
</div>
</div>
<div class="job_data_box">
<div class="job_data_label">
Transcoder Host:
</div>
<div class="job_data_value">
{transcoder}
</div>
</div>
<div class="job_page_steps_title_box">
<div class="job_page_steps_title">
Steps
</div>
</div>
{this.handleReverseArray()}
<div class="job_page_data_title_box">
<div class="job_page_data_title">
Data
</div>
</div>
{this.handleArrayData()}
<div class="job_page_json_title_box">
<div class="job_page_json_title">
Job Data as JSON
</div>
</div>
<div class="job_page_json_box">
<div class="job_page_json">
<SyntaxHighlighter language="json" style={gradientDark} wrapLines="true" wrapLongLines="true">
{JSON.stringify(this.state.vidispineData, null, 2)}
</SyntaxHighlighter>
</div>
</div>
</div>
</div>
)
}
}
export default withRouter(JobPage);