diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index f679d1c329051b0fd46a6af7b291e47addf1ac02..7c3754034dd89f124400554a78d8453ba98ae10f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -25,7 +25,7 @@ "jspdf": "^2.3.0", "jspdf-autotable": "^3.5.13", "katex": "^0.12.0", - "lodash": "^4.17.19", + "lodash": "^4.17.21", "match-sorter": "^4.1.0", "moment": "^2.27.0", "node-sass": "^4.12.0", @@ -45,7 +45,7 @@ "react-flatpickr": "^3.10.7", "react-frame-component": "^4.1.2", "react-json-to-table": "^0.1.7", - "react-json-view": "^1.19.1", + "react-json-view": "^1.21.3", "react-loader-spinner": "^3.1.14", "react-router-dom": "^5.2.0", "react-scripts": "^3.4.2", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss index 013ae6c00108792d2c30dc0245a65bd95d62cd1a..71777a6732a73c3755eb35cc9208b6641f33e245 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss @@ -8,10 +8,10 @@ } .report-table { - width: 100%; + width: 98%; table-layout: fixed; font-size: 10px; - word-break: break-word; + word-break: break-all; border: 1px solid; } @@ -22,4 +22,10 @@ .report-calendar span,.report-calendar span input { width: 100%; +} + +.report-download-bar { + float: right; + margin-left: 5px; + font-size: 20px; } \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/project.report.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/project.report.js index 94cdbb6e530a8a69b17d4cf045fb835edb702652..d6c34c872c9c02140fa51dd78a4a9446688005d3 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/project.report.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/project.report.js @@ -3,6 +3,7 @@ import { Link } from 'react-router-dom'; import moment from 'moment'; import _ from 'lodash'; import { Bar } from 'react-chartjs-2'; +import Papa from "papaparse"; import jsPDF from 'jspdf'; import html2canvas from 'html2canvas'; @@ -14,22 +15,22 @@ import { Calendar } from 'primereact/calendar'; import { Button } from 'primereact/button'; import CycleService from '../../services/cycle.service'; -const SU_DETAILS_COLUMNS = [{name: "su_name", headerTitle: "SU Name & Link in TMSS", propertyname: "name"}, - {name: "su_status", headerTitle: "SU Status Failed / Success ", propertyname: "status"}, - {name: "su_execDate", headerTitle: "SU Execution Date", propertyname: "exec_date"}, - {name: "observTime", headerTitle: "Time Observed (hr)", propertyname: "observingTime"}, - {name: "observTimeInc", headerTitle: "Time Observed Incremental (hr)", propertyname: "observingTimeInc"}, - {name: "observTimeLeft", headerTitle: "Time left for Observing (hr)", propertyname: "observingTimeLeft"}, - {name: "observTimeIncPercent", headerTitle: "Completed Observing Time(%)", propertyname: "observingTimeIncPercent"}, - {name: "processTime", headerTitle: "Time Processed (hr)", propertyname: "processTime"}, - {name: "processTimeInc", headerTitle: "Time Processed Incremental (hr)", propertyname: "processTimeInc"}, - {name: "processTimeLeft", headerTitle: "Time left for Processing (hr)", propertyname: "processTimeLeft"}, - {name: "processTimeIncPercent", headerTitle: "Completed Processing Time(%)", propertyname: "processTimeIncPercent"}, - {name: "ingestDate", headerTitle: "LTA Ingest Date", propertyname: "ingestDate"}, - {name: "ingestDataSize", headerTitle: "Ingested Data Size(TB)", propertyname: "ingestDataSize"}, - {name: "ingestDataIncPercent", headerTitle: "Used LTA Allocation (Incremental) (%)", propertyname: "ingestDataIncPercent"}, - {name: "observationSASId", headerTitle: "SAS ID (Observations)", propertyname: "observationSASId"}, - {name: "pipelinseSASId", headerTitle: "SAS ID (Pipelines)", propertyname: "pipelinseSASId"}, +const SU_DETAILS_COLUMNS = [{name: "su_name", headerTitle: "SU Name & Link in TMSS", propertyName: "name"}, + {name: "su_status", headerTitle: "SU Status Failed / Success ", propertyName: "status"}, + {name: "su_execDate", headerTitle: "SU Execution Date", propertyName: "exec_date"}, + {name: "observTime", headerTitle: "Time Observed (hr)", propertyName: "observingTime"}, + {name: "observTimeInc", headerTitle: "Time Observed Incremental (hr)", propertyName: "observingTimeInc"}, + {name: "observTimeLeft", headerTitle: "Time left for Observing (hr)", propertyName: "observingTimeLeft"}, + {name: "observTimeIncPercent", headerTitle: "Completed Observing Time(%)", propertyName: "observingTimeIncPercent"}, + {name: "processTime", headerTitle: "Time Processed (hr)", propertyName: "processTime"}, + {name: "processTimeInc", headerTitle: "Time Processed Incremental (hr)", propertyName: "processTimeInc"}, + {name: "processTimeLeft", headerTitle: "Time left for Processing (hr)", propertyName: "processTimeLeft"}, + {name: "processTimeIncPercent", headerTitle: "Completed Processing Time(%)", propertyName: "processTimeIncPercent"}, + {name: "ingestDate", headerTitle: "LTA Ingest Date", propertyName: "ingestDate"}, + {name: "ingestDataSize", headerTitle: "Ingested Data Size(TB)", propertyName: "ingestDataSize"}, + {name: "ingestDataIncPercent", headerTitle: "Used LTA Allocation (Incremental) (%)", propertyName: "ingestDataIncPercent"}, + {name: "observationSASId", headerTitle: "SAS ID (Observations)", propertyName: "observationSASId"}, + {name: "pipelinseSASId", headerTitle: "SAS ID (Pipelines)", propertyName: "pipelinseSASId"}, ] class ProjectReport extends Component { @@ -45,6 +46,7 @@ class ProjectReport extends Component { }; this.selectProject = this.selectProject.bind(this); this.loadProjectReport = this.loadProjectReport.bind(this); + this.downloadCSV = this.downloadCSV.bind(this); } componentDidMount() { @@ -125,10 +127,11 @@ class ProjectReport extends Component { // For testing set dummy value for LTA dataproducts reportSub["LTA dataproducts"] = {size__sum: 10737418240}; if (reportSub["LTA dataproducts"]) { - reportSub.ingestDataSize = (reportSub["LTA dataproducts"]["size__sum"]/dataSizeFactor).toFixed(2); + reportSub.ingestDataSize = ((reportSub["LTA dataproducts"]["size__sum"] || 0)/dataSizeFactor).toFixed(2); totalLTAStorage += reportSub["LTA dataproducts"]["size__sum"]; reportSub.ingestDataIncPercent = (totalLTAStorage / projectLTAStorage * 100).toFixed(2); } + delete reportSub["LTA dataproducts"]; } let observTimeUtilization = {type: 'Observing (hrs)', value: parseFloat((totalSUBObsTime/timeFactor).toFixed(2))}; resourceUtilization.push(observTimeUtilization); @@ -165,9 +168,9 @@ class ProjectReport extends Component { { SU_DETAILS_COLUMNS.map((column, colIndex) => { return ( <td key={(rowIndex+"_td_"+colIndex)}> - {column.propertyname === "name"?( - <Link to={`/schedulingunit/view/blueprint/${rowData['id']}`} target="_blank">{rowData[column.propertyname]}</Link> - ):rowData[column.propertyname]} + {column.propertyName === "name"?( + <Link to={`/schedulingunit/view/blueprint/${rowData['id']}`} target="_blank">{rowData[column.propertyName]}</Link> + ):rowData[column.propertyName]} </td> ) }) @@ -187,14 +190,68 @@ class ProjectReport extends Component { const input = document.getElementById('report-div'); html2canvas(input) .then((canvas) => { - const imgData = canvas.toDataURL('image/png'); - const pdf = new jsPDF(); - pdf.addImage(imgData, 'JPEG', 0, 0); - // pdf.output('dataurlnewwindow'); - pdf.save("project.pdf"); + const imgData = canvas.toDataURL('image/jpeg'); + const pdf = new jsPDF('l', 'px', [ (input.clientWidth+100), (input.clientHeight+100)]); + pdf.addImage(imgData, 'JPEG', 25, 25); + pdf.output('dataurlnewwindow'); + // pdf.save("project.pdf"); }); } + /** + * Function to download the report data in CSV format + */ + downloadCSV() { + let csvConfig = {}; + let csvList= []; + let colHeaders = []; + const reportData = this.state.reportData; + const projectResources = this.state.projectResources; + this.state.suStatsList.map((data, index) => { + let csvData = {}; + const colKeys = _.keys(data); + /* Add Project Details to the first row of the SU data */ + if (index === 0) { + colHeaders.push("Project"); + csvData["Project"] = this.state.projectName; + colHeaders.push("Contact Project Friend"); + csvData["Contact Project Friend"] = reportData.friends?reportData.friends.join(","):"-" + colHeaders.push("Awarded Observing Time(hours)"); + csvData["Awarded Observing Time(hours)"] = projectResources["LOFAR Observing Time"]?projectResources["LOFAR Observing Time"].convertedValue:"-"; + colHeaders.push("Awarded Processing Time(hours)"); + csvData["Awarded Processing Time(hours)"] = projectResources["CEP Processing Time"]?projectResources["CEP Processing Time"].convertedValue:"-"; + colHeaders.push("Awarded LTA Storage(TB)"); + csvData["Awarded LTA Storage(TB)"] = projectResources["LTA Storage"]?projectResources["LTA Storage"].convertedValue:"-"; + } + + // For every column of the data, replace the column name with the column title + for (const colKey of colKeys) { + let colHeader = _.find(SU_DETAILS_COLUMNS, ["propertyName", colKey]); + colHeader = colHeader?colHeader.headerTitle:_.upperFirst(colKey); + if (index === 0) { + colHeaders.push(colHeader); + } + csvData[colHeader] = data[colKey]; + } + csvList.push(csvData); + }); + if (colHeaders.length > 0) { + csvConfig.columns = colHeaders; + } + const csvString = Papa.unparse(csvList, csvConfig); + const blob = new Blob([csvString], { type: "text/csv" }); + var a = document.createElement("a"); + document.body.appendChild(a); + a.style = "display: none"; + + var url = window.URL.createObjectURL(blob); + a.href = url; + const reportPeriod = `${moment(this.state.reportPeriod[0]).format("DDMMMYYYY")}-${moment(this.state.reportPeriod[1]).format("DDMMMYYYY")}`; + a.download = `Project_${this.state.projectName}_${reportPeriod}.csv`; + a.click(); + window.URL.revokeObjectURL(url); + } + render() { const reportData = this.state.reportData; const project = this.state.project; @@ -265,17 +322,30 @@ class ProjectReport extends Component { <Calendar value={this.state.reportPeriod} selectionMode="range" dateFormat="yy-mm-dd" onChange={e => this.setState({reportPeriod: e.value})}></Calendar> </div> - <div className="col-lg-1 col-md-1 col-sm-12"> - <Button label="Generate" className="p-button-primary" icon="pi pi-check" + <div className="col-lg-2 col-md-1 col-sm-12"> + <Button label="" className="p-button-primary" icon="pi pi-check" tooltip="Generate Report" onClick={this.loadProjectReport} disabled={!this.state.projectName || !this.state.reportPeriod} /> - </div> {/* <i className="fa fa-download" onClick={(e) => this.downloadPDF()}></i> */} + {/* <i className="fa fa-download" onClick={(e) => this.downloadCSV()}></i> */} </div> {reportData && <> + <div> + <Link to={{}} className="report-download-bar" style={{color: "green"}} title="Download Report Data in CSV format" + onClick={this.downloadCSV}> + <i className="fas fa-file-csv"></i> + </Link> + <Link to={{}} className="report-download-bar" style={{color: "darkred"}} title="Download Report as PDF" + onClick={this.downloadPDF}> + <i className="fas fa-file-pdf"></i> + </Link> + + {/* <Link label="" className="p-button-primary" icon="fas fa-file-csv" tootltip="Download CSV" + onClick={this.downloadCSV} /> */} + </div> <div className="report-div" id="report-div"> - <h2 style={{textAlign: "center", marginBottom:"25px"}}>Report statistics for project {this.props.project}</h2> + <h2 style={{textAlign: "center", marginBottom:"25px"}}>Report statistics for project {this.state.projectName}</h2> <div className="p-grid"> <div className="col-lg-3 col-md-4 col-sm-12"> <label>Project Documentation</label>