diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss index b417a3392a75736c5173c70da62a1e264d15075b..424adcbe1314c1ccd47b02f88b115c8e45fcd2f1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/_overrides.scss @@ -245,6 +245,10 @@ In Excel View the for Accordion background color override border-color: transparent !important; } +.p-tabview-title { + display: inline !important; +} + /** Override the Primereact MultiSelect Dropdown Begin diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss index 162da9617a52b68cd91028960fdabd32e09cc305..6b3aa8ba4bfce76b3347d3d09e2e2667017a540d 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_layout.scss @@ -20,4 +20,5 @@ @import "./reservation"; @import "./animation"; @import "./workflow"; +@import "./_report"; // @import "./splitpane"; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss new file mode 100644 index 0000000000000000000000000000000000000000..55cb6eb47fec9df0b2a9f0654e25002e74d977ca --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_report.scss @@ -0,0 +1,21 @@ +.report-div { + font-size: 12px; +} + +.report-div label { + font-size: 12px; + color: black; +} + +.report-table { + width: 100%; + table-layout: fixed; + font-size: 10px; + word-break: break-all; + border: 1px solid; +} + +.report-table thead th, .report-table tbody td { + padding: 5px; + border: 1px solid; +} \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/index.js new file mode 100644 index 0000000000000000000000000000000000000000..2d0b9d35dd958f55af382ccb7f512152a9b192fd --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/index.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react'; +import { TabView,TabPanel } from 'primereact/tabview'; + +import PageHeader from '../../layout/components/PageHeader'; + +import ProjectReport from "./project.report"; + +class ReportHome extends Component { + + constructor(props) { + super(props); + this.state = { + reportIndex: 1 + } + this.close = this.close.bind(this); + } + + close() { + this.props.history.goBack(); + } + + render() { + return ( + <React.Fragment> + <PageHeader location={this.props.location} title={'Reports'} actions={[{icon:'fa-window-close', title:'Click to Close Report', + type: 'button', actOn: 'click', props:{ callback: this.close }}]}/> + <TabView activeIndex={this.state.reportIndex} onTabChange={(e) => this.setState({reportIndex: e.index})}> + <TabPanel header="Cycle"> + <h1>Cycle Report...</h1> + </TabPanel> + <TabPanel header="Project"> + <ProjectReport project="high"></ProjectReport> + </TabPanel> + <TabPanel header="Scheduling Unit"> + <h1>Scheduling Unit Report...</h1> + </TabPanel> + </TabView> + + </React.Fragment> + ); + } +} + +export default ReportHome; \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..a3582e903ecad590f718e3b5957ff9fa0a3a5fcf --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Report/project.report.js @@ -0,0 +1,210 @@ +import React, { Component } from 'react'; +import _ from 'lodash'; + +import ProjectService from '../../services/project.service'; +import ReportService from '../../services/report.service'; +import UnitConverter from '../../utils/unit.converter'; + +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 { + + constructor(props) { + super(props); + this.state = { + project: null, + reportData: null, + projectResources: {}, + suStatsList: [] + }; + + } + + componentDidMount() { + if (this.props.project) { + ProjectService.getProjectDetails(this.props.project) + .then(async(project) => { + if (project) { + const resourceList = await ProjectService.getResources(); + const projectReport = await ReportService.getProjectReport(this.props.project); + let projectResources = {}; + if (projectReport.error) { + console.log(projectReport.error); + } else { + for (let quota of projectReport.quota) { + const resource = _.find(resourceList, ["name", quota.resource_type_id]); + const conversionFactor = UnitConverter.resourceUnitMap[resource.quantity_value]?UnitConverter.resourceUnitMap[resource.quantity_value].conversionFactor:1; + quota.convertedValue = (quota.value / conversionFactor).toFixed(2); + projectResources[quota.resource_type_id] = quota; + } + } + console.log(projectReport); + let suStatsList = []; + if (projectReport["SUBs"]) { + const projectObservingTime = projectResources["LOFAR Observing Time"]?projectResources["LOFAR Observing Time"].value:0; + const projectProcessTime = projectResources["CEP Processing Time"]?projectResources["CEP Processing Time"].value:0; + const projectLTAStorage = projectResources["LTA Storage"]?projectResources["LTA Storage"].value:0; + const timeFactor = UnitConverter.resourceUnitMap["time"].conversionFactor; + const dataSizeFactor = UnitConverter.resourceUnitMap["bytes"].conversionFactor; + let totalSUBObsTime = 0, totalProcessTime = 0, totalLTAStorage = 0; + for (const subStatus of _.keys(projectReport["SUBs"])) { + let subs = projectReport["SUBs"][subStatus]; + for (const sub of subs) { + let reportSub = _.cloneDeep(sub); + reportSub.status = subStatus; + + if (sub.duration) { + reportSub.observingTime = (sub.duration/timeFactor).toFixed(2); + totalSUBObsTime += sub.duration; + reportSub.observingTimeInc = (totalSUBObsTime / timeFactor).toFixed(2); + reportSub.observingTimeLeft = ((projectObservingTime - totalSUBObsTime)/timeFactor).toFixed(2); + reportSub.observingTimeIncPercent = (totalSUBObsTime/projectObservingTime*100).toFixed(2); + } + // For testing set duration as processDuration + sub.processDuration = sub.duration; + if (sub.processDuration) { + reportSub.processTime = (sub.processDuration/timeFactor).toFixed(2); + totalProcessTime += sub.processDuration; + reportSub.processTimeInc = (totalProcessTime / timeFactor).toFixed(2); + reportSub.processTimeLeft = ((projectProcessTime - totalProcessTime)/timeFactor).toFixed(2); + reportSub.processTimeIncPercent = (totalProcessTime / projectProcessTime *100).toFixed(2); + } + // For testing set dummy value for LTA dataproducts + sub["LTA dataproducts"] = {size__sum: 10737418240}; + if (sub["LTA dataproducts"]) { + reportSub.ingestDataSize = (sub["LTA dataproducts"]["size__sum"]/dataSizeFactor).toFixed(2); + totalLTAStorage += sub["LTA dataproducts"]["size__sum"]; + reportSub.ingestDataIncPercent = (totalLTAStorage / projectLTAStorage * 100).toFixed(2); + } + + suStatsList.push(reportSub); + } + } + console.log(suStatsList); + } + this.setState({project: project, reportData: projectReport, projectResources: projectResources, suStatsList: suStatsList}); + } + }); + } + } + + renderSUTableHeader() { + return ( + <thead><tr> + { + SU_DETAILS_COLUMNS.map((item, index) => { + return <th key={("th_"+index)}>{item.headerTitle}</th> + }) + } + </tr></thead> + ); + } + + renderSURows() { + const suStatsList = this.state.suStatsList; + return ( + <> + { + suStatsList.map((rowData, rowIndex) => { + return ( + <tr key={("tr_"+rowIndex)} style={{border: "1px solid"}}> + { SU_DETAILS_COLUMNS.map((column, colIndex) => { + return ( + <td key={(rowIndex+"_td_"+colIndex)}>{rowData[column.propertyname]}</td> + ) + }) + } + </tr> + ) + }) + } + </> + ); + } + + render() { + const reportData = this.state.reportData; + const project = this.state.project; + return( + <React.Fragment> + {reportData && + <div className="report-div"> + <h2 style={{textAlign: "center", marginBottom:"25px"}}>Report statistics for project {this.props.project}</h2> + <div className="p-grid"> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Project Documentation</label> + </div> + <div className="col-lg-9 col-md-8 col-sm-12"> + <a href="">Link to Jira Ticket</a> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Project statistics over the perriod</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>June 01 2021 - Apr 15 2022</span> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Contact Project Friend</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>{reportData.friends?reportData.friends.join(","):"-"}</span> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Awarded Observing Time(hours)</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>{this.state.projectResources["LOFAR Observing Time"]?this.state.projectResources["LOFAR Observing Time"].convertedValue:"-"}</span> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Awarded Processing Time(hours)</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>{this.state.projectResources["CEP Processing Time"]?this.state.projectResources["CEP Processing Time"].convertedValue:"-"}</span> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Awarded LTA Storage(TB)</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>{this.state.projectResources["LTA Storage"]?this.state.projectResources["LTA Storage"].convertedValue:"-"}</span> + </div> + <div className="col-lg-3 col-md-4 col-sm-12"> + <label>Release Date</label> + </div> + <div className="col-lg-8 col-md-9 col-sm-12"> + <span>{project.releaseDate?project.releaseDate:"-"}</span> + </div> + </div> + + <div className="su_details"> + <table className="report-table"> + {this.renderSUTableHeader()} + <tbody> + {this.renderSURows()} + </tbody> + </table> + </div> + </div> + } + </React.Fragment> + ); + } + +} + +export default ProjectReport; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index aa2ce1fc7a253bf50bfc8f524aedbe3beffc5773..f37da9a3fde84005683c5a5373aa166644a24064 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -19,6 +19,7 @@ import { ReservationCreate, ReservationList, ReservationView, ReservationEdit } import { FindObjectResult } from './Search/' import SchedulingSetCreate from './Scheduling/excelview.schedulingset'; import Workflow from './Workflow'; +import ReportHome from './Report'; import { Growl } from 'primereact/components/growl/Growl'; import { setAppGrowl } from '../layout/components/AppGrowl'; import WorkflowList from './Workflow/workflow.list' @@ -192,6 +193,12 @@ export const routes = [ component: WorkflowList, name: 'Workflow', title: 'Workflow' + }, + { + path: "/report", + component: ReportHome, + name: 'Report', + title: 'Report' } ]; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js new file mode 100644 index 0000000000000000000000000000000000000000..d0224c3f1f3943ec64498d931ab6ecef69156488 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/report.service.js @@ -0,0 +1,18 @@ +import axios from "axios"; + +const ReportService = { + + getProjectReport: async(project) => { + let reportData = {}; + try { + const response = await axios.get(`/api/project/${project}/report/`); + reportData = response.data; + } catch(error) { + console.error(error); + reportData.error = error; + } + return reportData; + } +} + +export default ReportService; \ No newline at end of file