diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index 11b49d269318768f1595a2d1e95c4354b6b66544..69db9ad13a90ad324e34161dcd75df86581af5ad 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -46,7 +46,7 @@ "test": "react-scripts test", "eject": "react-scripts eject" }, - "proxy": "http://127.0.0.1:8008/", + "proxy": "http://192.168.99.100:8008/", "eslintConfig": { "extends": "react-app" }, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js index c134771ba94f98aca6b1b3dae8ec713ca0f9555b..c3d6d2e02ae83cb347aa9ab260d0ffe3b2866fab 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/components/ViewTable.js @@ -8,10 +8,13 @@ import {OverlayPanel} from 'primereact/overlaypanel'; import {InputSwitch} from 'primereact/inputswitch'; import { Calendar } from 'primereact/calendar'; import {Paginator} from 'primereact/paginator'; +import { Button } from "react-bootstrap"; +import { InputNumber } from "primereact/inputnumber"; let tbldata =[]; let isunittest = false; let columnclassname =[]; + // Define a default UI for filtering function GlobalFilter({ preGlobalFilteredRows, @@ -229,7 +232,7 @@ const IndeterminateCheckbox = React.forwardRef( ) // Our table component -function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn, tablename }) { +function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn, tablename, defaultpagesize }) { const filterTypes = React.useMemo( () => ({ // Add a new fuzzyTextFilterFn filter type. @@ -280,6 +283,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn defaultColumn, filterTypes, initialState: { pageIndex: 0, + pageSize: (defaultpagesize && defaultpagesize>0)?defaultpagesize:10, sortBy: defaultSortColumn } }, useFilters, @@ -296,13 +300,37 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn let op = useRef(null); const [currentpage, setcurrentPage] = React.useState(0); - const [currentrows, setcurrentRows] = React.useState(10); - + const [currentrows, setcurrentRows] = React.useState(defaultpagesize); + const [custompagevalue,setcustompagevalue] = React.useState(); + const onPagination = (e) => { gotoPage(e.page); setcurrentPage(e.first); setcurrentRows(e.rows); setPageSize(e.rows) + if([10,25,50,100].includes(e.rows)){ + setcustompagevalue(); + } + }; + + const onCustomPage = (e) => { + if(typeof custompagevalue === 'undefined' || custompagevalue == null) return; + gotoPage(0); + setcurrentPage(0); + setcurrentRows(custompagevalue); + setPageSize(custompagevalue) + }; + + const onChangeCustompagevalue = (e) => { + setcustompagevalue(e.target.value); + } + + const onShowAllPage = (e) => { + gotoPage(e.page); + setcurrentPage(e.first); + setcurrentRows(e.rows); + setPageSize(tbldata.length) + setcustompagevalue(); }; const onToggleChange = (e) =>{ @@ -326,7 +354,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn <div style={{textAlign: 'center'}}> <label>Select column(s) to view</label> </div> - <div style={{float: 'left', backgroundColor: '#d1cdd936', width: '250px', height: '400px', overflow: 'auto', marginBottom:'10px', padding:'5px'}}> + <div style={{float: 'left', backgroundColor: '#d1cdd936', width: '250px', minHeight: '100px', maxHeight: '300px' , overflow: 'auto', marginBottom:'10px', padding:'5px'}}> <div id="tagleid" > <div > <div style={{marginBottom:'5px'}}> @@ -358,6 +386,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn /> } </div> + <div className="total_records_top_label"> <label >Total records ({data.length})</label></div> </div> <div className="table_container"> @@ -396,7 +425,7 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn if(cell.column.id !== 'actionpath') return <td {...cell.getCellProps()}>{cell.render('Cell')}</td> else - return; + return ""; })} </tr> ) @@ -405,7 +434,17 @@ function Table({ columns, data, defaultheader, optionalheader, defaultSortColumn </table> </div> <div className="pagination"> - <Paginator rowsPerPageOptions={[10,25,50,100]} first={currentpage} rows={currentrows} totalRecords={rows.length} onPageChange={onPagination}></Paginator> + <div className="total_records_bottom_label" ><label >Total records ({data.length})</label></div> + <div> + <Paginator rowsPerPageOptions={[10,25,50,100]} first={currentpage} rows={currentrows} totalRecords={rows.length} onPageChange={onPagination} > </Paginator> + </div> + <div> + <InputNumber id="custompage" value={custompagevalue} onChange ={onChangeCustompagevalue} + min={0} style={{width:'100px'}} /> + <label >Records/Page</label> + <Button onClick={onCustomPage}> Show </Button> + <Button onClick={onShowAllPage} style={{marginLeft: "1em"}}> Show All </Button> + </div> </div> </> ) @@ -441,7 +480,7 @@ function ViewTable(props) { if(!defaultSortColumn){ defaultSortColumn =[{}]; } - + let defaultpagesize = (typeof props.defaultpagesize === 'undefined' || props.defaultpagesize == null)?10:props.defaultpagesize; let columns = []; let defaultdataheader = Object.keys(defaultheader[0]); let optionaldataheader = Object.keys(optionalheader[0]); @@ -505,6 +544,7 @@ optionaldataheader.forEach(header => { columns.forEach(column =>{ togglecolumns.filter(tcol => { column.isVisible = (tcol.Header === column.Header)?tcol.isVisible:column.isVisible; + return tcol; }) }) } @@ -540,7 +580,7 @@ optionaldataheader.forEach(header => { return ( <div> <Table columns={columns} data={tbldata} defaultheader={defaultheader[0]} optionalheader={optionalheader[0]} - defaultSortColumn={defaultSortColumn} tablename={tablename}/> + defaultSortColumn={defaultSortColumn} tablename={tablename} defaultpagesize={defaultpagesize}/> </div> ) } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss index 1d8e0e084971ed7a554ed2b8511dbcc30f07cf41..deea6383b117930d698cb8463ba1caff2d20fc21 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss +++ b/SAS/TMSS/frontend/tmss_webapp/src/layout/sass/_viewtable.scss @@ -36,14 +36,20 @@ } .pagination { - display: block !important; + margin-top: .25em; + display: flex; + justify-content: center; + background-color: #ebeaea; + border: none; + border-bottom: 1px solid lightgray; + border-top: 1px solid lightgray; } body .p-paginator { + margin-top: .25em; + margin-bottom: .15em; background-color: #ebeaea; border: none; - border-bottom: 1px solid lightgray; - border-top: 1px solid lightgray; } .p-paginator .p-paginator-icon { @@ -68,7 +74,28 @@ body .p-paginator { border-color: black; border: black; } - + +.pagination span { + margin-bottom: 0px; +} + +.pagination input { + margin-top: .25em; + margin-bottom: .15em; + margin-left: 1em; + margin-right: 0.75em; + width: 5em; + height: 2.25em; + border: none; + text-align: center; +} + +.pagination button { + margin-left: 5px; + height: 35px; + + margin-bottom: .15em; +} .filter-input input{ max-width: 175px; } @@ -101,20 +128,11 @@ body .p-paginator { padding-left: 3px; } -/* }.pagination button { - margin-left: 3px; - background-color: #005b9f; - border: 1px solid #005b9f; - border-radius: 4px; - color: white; - font-weight: 900; +.total_records_bottom_label { + text-align: left; + margin-right: 20px; + margin-top: 7px; +} +.total_records_top_label { + margin-left: 15px; } - -.pagination button:disabled { - margin-left: 3px; - background-color: #c8c9c9; - border: 1px solid #c8c9c9; - border-radius: 4px; - color: white; -} */ - diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js index 468139893f385631048f5b024df39bb65bcb80bc..61f385ed7b7e6360a1a0775ec6880e4bc8d91392 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Cycle/view.js @@ -96,8 +96,8 @@ export class CycleView extends Component { </div> */ } <PageHeader location={this.props.location} title={'Cycle - Details'} actions={[ {icon:'fa-edit', title:'Click to Edit Cycle', props:{ pathname: `/cycle/edit/${this.state.cycle.name}`, - state: {id: this.state.cycle?this.state.cycle.name:''}}}, - {icon: 'fa-window-close',props:{ pathname: `/cycle`}}]}/> + state: {id: this.state.cycle?this.state.cycle.name:''}}}, + {icon: 'fa-window-close',props:{ pathname: `/cycle`}}]}/> { this.state.isLoading && <AppLoader /> } { this.state.cycle && <React.Fragment> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/dataproduct.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/dataproduct.js new file mode 100644 index 0000000000000000000000000000000000000000..62996534798c14247bf60e8e9064baea919f83a3 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/dataproduct.js @@ -0,0 +1,133 @@ +import React, {Component} from 'react'; +import {Link} from 'react-router-dom' +import AppLoader from '../../layout/components/AppLoader'; +import DataProductService from '../../services/data.product.service'; +import TaskService from '../../services/task.service'; +import ViewTable from './../../components/ViewTable'; +import UnitConverter from './../../utils/unit.converter' + +export class DataProduct extends Component{ + constructor(props){ + super(props); + this.state = { + isLoading: true, + dataproduct:[], + defaultcolumns: [ { + "type":"Type", + "filename":"File Name", + "fullpath":"File Path", + "storagelocation":"Storage Location", + "size":"Size (TB)", + "completed":"Completed %", + "deleted_since":"Deleted at", + }], + optionalcolumns: [{ + }], + columnclassname: [{ + "Type" : "filter-input-50", "Completed %" : "filter-input-50", "Size (TB)": "filter-input-50", + "Deleted at" : "filter-input-150","Storage Location" : "filter-input-125" + }], + defaultSortColumn: [{id: "File Name", desc: true}], + } + + if (this.props.match.params.id) { + this.state.taskId = this.props.match.params.id; + this.state.taskType = 'blueprint'; + } + } + + componentDidMount(){ + this.getDataProduct(this.state.taskId, this.state.taskType); + } + + /* + Fetch Data Product for the Task, data are getting like below + */ + async getDataProduct(id, type){ + // Task type = blueprint + await TaskService.getTaskDetails(type, id).then(async taskBlueprint =>{ + let subTaskIds = taskBlueprint['subtasks_ids']; + if(subTaskIds){ + let dataproducts = []; + for(const id of subTaskIds){ + let storageLocation = ''; + await DataProductService.getSubtask(id).then( subtask =>{ + storageLocation = subtask.data.cluster_value; + }) + //Fetch data product for Input Subtask and Output Subtask + await DataProductService.getSubtaskInputDataproduct(id).then(async inputdata =>{ + for(const dataproduct of inputdata.data){ + dataproduct['type'] = 'Input'; + dataproduct['size'] = UnitConverter.getUIResourceUnit('bytes', dataproduct['size']); + dataproduct['fullpath'] = dataproduct['directory']; + dataproduct['storagelocation'] = storageLocation; + dataproducts.push(dataproduct); + } + }).then( + await DataProductService.getSubtaskOutputDataproduct(id).then(outputdata =>{ + for(const dataproduct of outputdata.data){ + dataproduct['type'] = 'Output'; + dataproduct['size'] = UnitConverter.getUIResourceUnit('bytes', dataproduct['size']); + dataproduct['fullpath'] = dataproduct['directory']; + dataproduct['storagelocation'] = storageLocation; + dataproducts.push(dataproduct); + } + }) + ) + } + this.setState({ + dataproduct: dataproducts, + task: taskBlueprint, + isLoading: false, + }) + + } + }) + } + + render(){ + return( + <React.Fragment> + <div className="p-grid"> + <div className="p-col-10 p-lg-10 p-md-10"> + <h2> Data Product - {this.state.task && + <Link to={ { pathname:`/task/view/blueprint/${this.state.taskId}`}}> {this.state.task.name}</Link> + } </h2> + </div> + <div className="p-col-2"> + {this.state.task && + <Link to={{ pathname:`/task/view/blueprint/${this.state.taskId}`}} title="Close" + style={{float:'right'}}> + <i className="fa fa-times" style={{marginTop: "10px", marginLeft: '5px'}}></i> + </Link> + } + </div> + </div> + + {this.state.isLoading? <AppLoader /> : + <React.Fragment> + {(!this.state.dataproduct || this.state.dataproduct.length === 0) && + <div > No data found!</div> + } + {this.state.dataproduct.length>0 && + <ViewTable + data={this.state.dataproduct} + defaultcolumns={this.state.defaultcolumns} + optionalcolumns={this.state.optionalcolumns} + columnclassname={this.state.columnclassname} + defaultSortColumn={this.state.defaultSortColumn} + showaction="false" + keyaccessor="id" + paths={this.state.paths} + defaultpagesize={this.state.dataproduct.length} + unittest={this.state.unittest} + /> + } + </React.Fragment> + + } + </React.Fragment> + ) + } +} + \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js index d7e2c03a6532dbb07064d9ca603f5796bbedd986..91955b294875ad02e7bba6314ccadeac920920f1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/index.js @@ -1,4 +1,5 @@ import {TaskEdit} from './edit'; import {TaskView} from './view'; +import {DataProduct} from './dataproduct'; -export {TaskEdit, TaskView} ; +export {TaskEdit, TaskView, DataProduct} ; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js index 8fba525a164738a9c539e668eff231345742deb9..f5b80baaea6d1a8cde5731dc682ff3b7acdd27f9 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Task/view.js @@ -81,7 +81,7 @@ export class TaskView extends Component { .then((task) => { if (task) { TaskService.getSchedulingUnit(taskType, (taskType==='draft'?task.scheduling_unit_draft_id:task.scheduling_unit_blueprint_id)) - .then((schedulingUnit) => {console.log('schedulingUnit' ,schedulingUnit) + .then((schedulingUnit) => { let path = _.join(['/schedulingunit','view',((this.state.taskType === "draft")?'draft':'blueprint'),schedulingUnit.id], '/'); this.setState({schedulingUnit: schedulingUnit, supath:path}); }); @@ -228,6 +228,16 @@ export class TaskView extends Component { } </div> </div> + {this.state.taskType === 'blueprint' && + <div className="p-grid"> + <label className="col-lg-2 col-md-2 col-sm-12">Data Product</label> + <div className="col-lg-4 col-md-4 col-sm-12"> + + <Link to={ { pathname:`/task/view/blueprint/${this.state.taskId}/dataproducts`}}> View Data Product</Link> + </div> + + </div> + } <div className="p-fluid"> <div className="p-grid"><div className="p-col-12"> {this.state.taskTemplate?jeditor:""} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index bebe99d8eb04460c38b2097e576b94ce6d5dfc16..63e56d5b57ec44c2f90ac1daf48703f3084c3f23 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -9,8 +9,8 @@ import {NotFound} from '../layout/components/NotFound'; import {ProjectList, ProjectCreate, ProjectView, ProjectEdit} from './Project'; import {Dashboard} from './Dashboard'; import {Scheduling} from './Scheduling'; -import {TaskEdit, TaskView} from './Task'; -import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit'; +import {TaskEdit, TaskView, DataProduct} from './Task'; +import ViewSchedulingUnit from './Scheduling/ViewSchedulingUnit' import SchedulingUnitCreate from './Scheduling/create'; import { CycleList, CycleCreate, CycleView, CycleEdit } from './Cycle'; @@ -120,6 +120,11 @@ export const routes = [ name: 'Cycle List', title:'Cycle-List' }, + { + path: "/task/view/blueprint/:id/dataproducts", + component: DataProduct, + name: 'Data Product' + } ]; export const RoutedContent = () => { diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/data.product.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/data.product.service.js new file mode 100644 index 0000000000000000000000000000000000000000..bfae26441c89541577dd08bd64ba82dfdf049d66 --- /dev/null +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/data.product.service.js @@ -0,0 +1,48 @@ +const axios = require('axios'); + +//axios.defaults.baseURL = 'http://192.168.99.100:8008/api'; +axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0'; + +const DataProductService = { + + getSubtaskInputDataproduct: async function(id){ + try { + const url = `/api/subtask/${id}/input_dataproducts/`; + const response = axios.get(url); + return response; + } catch (error) { + console.error('[data.product.getSubtaskInputDataproduct]',error); + } + }, + getSubtaskOutputDataproduct: async function(id){ + try { + const url = `/api/subtask/${id}/output_dataproducts/`; + const response = axios.get(url); + return response; + } catch (error) { + console.error('[data.product.getSubtaskOutputDataproduct]',error); + } + }, + getSubTaskTypes: async function(id){ + try { + const url = `/api/subtask_template/${id}`; + const response = axios.get(url); + return response; + } catch (error) { + console.error('[data.product.getSubTaskTypes]',error); + } + }, + getSubtask: async function(id){ + try { + const url = `/api/subtask/${id}`; + const response = axios.get(url); + return response; + } catch (error) { + console.error('[data.product.getSubtask]',error); + } + + } + +} + +export default DataProductService; \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js index 6bb71382bbb5489bfa57ad3e14f77059b7219b55..f9479f4977a3f190cb0e1f7e221f3110068d959f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/schedule.service.js @@ -1,6 +1,5 @@ import axios from 'axios' import moment from 'moment'; - import TaskService from './task.service'; axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0'; @@ -84,8 +83,12 @@ const ScheduleService = { }, getTasksBySchedulingUnit: async function(id){ let scheduletasklist=[]; + // let taskblueprints = []; // Common keys for Task and Blueprint let commonkeys = ['id','created_at','description','name','tags','updated_at','url','do_cancel','relative_start_time','relative_stop_time','start_time','stop_time','duration']; + // await this.getTaskBlueprints().then( blueprints =>{ + // taskblueprints = blueprints.data.results; + // }); await this.getTasksDraftBySchedulingUnitId(id) .then(async(response) =>{ for(const task of response.data.results){ @@ -102,7 +105,11 @@ const ScheduleService = { scheduletask.relative_start_time = moment.utc(scheduletask.relative_start_time*1000).format('HH:mm:ss'); scheduletask.relative_stop_time = moment.utc(scheduletask.relative_stop_time*1000).format('HH:mm:ss'); //Fetch blueprint details for Task Draft - const draftBlueprints = await TaskService.getDraftsTaskBlueprints(task.id); + const draftBlueprints = await TaskService.getDraftsTaskBlueprints(task.id); + // let filteredblueprints = _.filter(taskblueprints, function(o) { + // if (o.draft_id === task['id']) return o; + // }); + for(const blueprint of draftBlueprints){ let taskblueprint = []; taskblueprint['tasktype'] = 'Blueprint';