Skip to content
Snippets Groups Projects
Commit c4ffdc44 authored by Ramesh Kumar's avatar Ramesh Kumar
Browse files

TMS-275: Add new Project functionality developed with initial requirements.

parent 76fc6e0b
No related branches found
No related tags found
1 merge request!191Resolve TMSS-243
Showing
with 1337 additions and 1 deletion
......@@ -32,7 +32,7 @@
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"react-table": "^7.2.1",
"react-transition-group": "^1.2.1",
"react-transition-group": "^2.5.1",
"reactstrap": "^8.5.1",
"styled-components": "^5.1.1",
"typescript": "^3.9.5",
......@@ -59,5 +59,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"customize-cra": "^0.9.1",
"react-app-rewired": "^1.6.2"
}
}
......@@ -94,6 +94,14 @@ p {
color: #28289b;
}
.p-multiselect-label {
margin-bottom: 0px;
}
.resource-input-grid div {
margin-bottom: 1rem;
}
.fa {
color: #005b9f;
}
......@@ -112,6 +120,42 @@ thead {
border-color: #dc3545 !important;
}
.pi-primary {
color: #007ad9;
}
.pi-warning {
color: #ffba01;
}
.pi-success {
color: #34A835;
}
.pi-info {
color: #008fba;
}
.pi-error {
color: #e91224;
}
.pi-small {
font-size: rem !important;
}
.pi-medium {
font-size: 1.5rem !important;
}
.pi-large {
font-size: 2rem !important;
}
.pi-x-large {
font-size: 3rem !important;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
......
import 'react-app-polyfill/ie11';
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
......
import React, {Component} from 'react';
import {InputNumber} from 'primereact/inputnumber';
/**
* Component to get input for Resource allocation while creating and editing Project
*/
export class ResourceInputList extends Component {
constructor(props) {
super(props);
this.state = {
list: props.list,
projectQuota: props.projectQuota
}
this.updateEnabled = this.props.list.length===0?true:false;
this.onInputChange = this.onInputChange.bind(this);
}
shouldComponentUpdate() {
return this.updateEnabled;
}
onInputChange(field, event) {
if (this.props.callback) {
this.props.callback(field, event);
}
}
render(){
return (
<>
{this.props.list.length>0 && this.props.list.map((item, index) => (
<React.Fragment key={index+10}>
<label key={'label1-'+ index} className="col-lg-3 col-md-3 col-sm-12">{item.name}</label>
<div key={'div1-'+ index} className="col-lg-3 col-md-3 col-sm-12">
<InputNumber key={'item1-'+ index} id={'item1-'+ index} name={'item1-'+ index}
suffix={` ${this.props.unitMap[item.resourceUnit.name].display}`}
placeholder={` ${this.props.unitMap[item.resourceUnit.name].display}`}
value={this.state.projectQuota[item.name]}
onBlur={(e) => this.onInputChange(item.name, e)}
/>
</div>
</React.Fragment>
))}
</>
);
}
}
\ No newline at end of file
This diff is collapsed.
import React from 'react';
import { BrowserRouter as Router } from 'react-router-dom';
import { act } from "react-dom/test-utils";
import { render, cleanup, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import {ProjectCreate} from './create';
import ProjectService from '../../services/project.service';
import CycleService from '../../services/cycle.service';
let projectCategoriesSpy, allCycleSpy, periodCategoriesSpy, saveProjectSpy;
beforeEach(() => {
setMockSpy();
});
afterEach(() => {
// cleanup on exiting
clearMockSpy();
cleanup();
});
/**
* To set mock spy for Services that have API calls to the back end to fetch data
*/
const setMockSpy = (() => {
projectCategoriesSpy = jest.spyOn(ProjectService, 'getProjectCategories');
projectCategoriesSpy.mockImplementation(() => { return Promise.resolve([{id: 1, name: 'Regular'}])});
periodCategoriesSpy = jest.spyOn(ProjectService, 'getPeriodCategories');
periodCategoriesSpy.mockImplementation(() => { return Promise.resolve([{id: 1, name: 'Single Cycle'}])});
allCycleSpy = jest.spyOn(CycleService, 'getAllCycles');
allCycleSpy.mockImplementation(() => {
return Promise.resolve([{url: "http://localhost:3000/api/cycle/Cycle-0", name: 'Cycle-0'},
{url: "http://localhost:3000/api/cycle/Cycle-1", name: 'Cycle-1'}]);
});
saveProjectSpy = jest.spyOn(ProjectService, 'saveProject');
saveProjectSpy.mockImplementation((project, projectQuota) => {
project.url = `http://localhost:3000/api/project/${project.name}`;
return Promise.resolve(project)
});
});
const clearMockSpy = (() => {
projectCategoriesSpy.mockRestore();
periodCategoriesSpy.mockRestore();
allCycleSpy.mockRestore();
saveProjectSpy.mockRestore();
});
it("renders without crashing with all back-end data loaded", async () => {
console.log("renders without crashing with all back-end data loaded ------------------------");
let content;
await act(async () => {
content = render(<Router><ProjectCreate /></Router>);
});
expect(content.queryByText('Project - Add')).not.toBe(null); // Page loaded successfully
expect(projectCategoriesSpy).toHaveBeenCalled(); // Mock Spy called successfully
expect(content.queryByText('Regular')).toBeInTheDocument(); // Project Category Dropdown loaded successfully
expect(content.queryByText('Single Cycle')).toBeInTheDocument(); // Period Category Dropdown loaded successfully
expect(content.queryByText('Cycle-0')).toBeInTheDocument(); // Cycle multi-select loaded successfully
});
it("Save button disabled initially when no data entered", async () => {
console.log("Save button disabled initially when no data entered -----------------------");
let content;
await act(async () => {
content = render(<Router><ProjectCreate /></Router>);
});
expect(content.queryByTestId('save-btn')).toHaveAttribute("disabled");
});
it("Save button enabled when mandatory data entered", async () => {
console.log("Save button enabled when mandatory data entered -----------------------");
let content;
await act(async () => {
content = render(<Router><ProjectCreate /></Router>);
});
const nameInput = content.queryByTestId('name');
const descInput = content.queryByTestId('description');
const spinButtons = content.queryAllByRole("spinbutton");
const rankInput = spinButtons.filter(function(element) { return element.id==="proj-rank"})[0];
// Set values for all mandatory input and test if save button is enabled
fireEvent.change(nameInput, { target: { value: 'OSR' } });
expect(nameInput.value).toBe("OSR");
fireEvent.change(descInput, { target: { value: 'OSR' } });
expect(descInput.value).toBe("OSR");
fireEvent.blur(rankInput, { target: { value: 1 } });
expect(rankInput.value).toBe("1");
expect(content.queryByTestId('save-btn').hasAttribute("disabled")).toBeFalsy();
});
it("renders Save button enabled when all data entered", async () => {
console.log("renders Save button enabled when all data entered -----------------------");
let content;
await act(async () => {
content = render(<Router><ProjectCreate /></Router>);
});
const nameInput = content.queryByTestId('name');
const descInput = content.queryByTestId('description');
const spinButtons = content.queryAllByRole("spinbutton");
const rankInput = spinButtons.filter(function(element) { return element.id==="proj-rank"})[0];
const trigPrioInput = spinButtons.filter(function(element) { return element.id==="trig_prio"})[0];
const trigger = content.getByLabelText(/trigger/i);
const projCatInput = content.getAllByRole("listbox")[0].children[0] ;
const projPeriodInput = content.getAllByRole("listbox")[1].children[0] ;
const cycleInput = content.getAllByRole("listbox")[2].children[0] ;
fireEvent.change(nameInput, { target: { value: 'OSR' } });
expect(nameInput.value).toBe("OSR");
fireEvent.change(descInput, { target: { value: 'OSR' } });
expect(descInput.value).toBe("OSR");
fireEvent.blur(rankInput, { target: { value: 1 } });
expect(rankInput.value).toBe("1");
expect(trigPrioInput.value).toBe("1000"); // Check for default value
fireEvent.blur(trigPrioInput, { target: { value: 100 } });
expect(trigPrioInput.value).toBe("100"); // Check for new value
fireEvent.click(trigger);
expect(trigger.hasAttribute("checked")).toBeTruthy();
// Before selecting Project Category
expect(content.queryAllByText('Select Project Category').length).toBe(2);
expect(content.queryAllByText('Regular').length).toBe(1);
expect(content.getAllByRole("listbox")[0].children.length).toBe(1);
fireEvent.click(projCatInput);
// After selecting Project Category
expect(content.queryAllByText('Select Project Category').length).toBe(1);
expect(content.queryAllByText('Regular').length).toBe(3);
// Before selecting Period Category
expect(content.queryAllByText('Select Period Category').length).toBe(2);
expect(content.queryAllByText('Single Cycle').length).toBe(1);
expect(content.getAllByRole("listbox")[1].children.length).toBe(1);
fireEvent.click(projPeriodInput);
// After selecting Period Category
expect(content.queryAllByText('Select Period Category').length).toBe(1);
expect(content.queryAllByText('Single Cycle').length).toBe(3);
// Before selecting Cycle
expect(content.queryAllByText('Cycle-0').length).toBe(1);
expect(content.getAllByRole("listbox")[2].children.length).toBe(2);
fireEvent.click(cycleInput);
// After selecting Cycle
expect(content.queryAllByText('Cycle-0').length).toBe(2);
expect(content.queryByTestId('save-btn').hasAttribute("disabled")).toBeFalsy();
// });
});
it("save project with mandatory fields", async () => {
console.log("save project -----------------------");
let content;
await act(async () => {
content = render(<Router><ProjectCreate /></Router>);
});
const nameInput = content.queryByTestId('name');
const descInput = content.queryByTestId('description');
const spinButtons = content.queryAllByRole("spinbutton");
const rankInput = spinButtons.filter(function(element) { return element.id==="proj-rank"})[0];
fireEvent.change(nameInput, { target: { value: 'OSR' } });
expect(nameInput.value).toBe("OSR");
fireEvent.change(descInput, { target: { value: 'OSR' } });
expect(descInput.value).toBe("OSR");
fireEvent.blur(rankInput, { target: { value: 1 } });
expect(rankInput.value).toBe("1");
expect(content.queryByTestId('save-btn').hasAttribute("disabled")).toBeFalsy();
expect(content.queryByTestId('projectId').value).toBe("");
expect(content.queryByText("Success")).toBe(null);
await act(async () => {
fireEvent.click(content.queryByTestId('save-btn'));
});
// After saving project, URL should be available and Success dialog should be displayed
expect(content.queryByTestId('projectId').value).toBe("http://localhost:3000/api/project/OSR");
expect(content.queryByText("Success")).not.toBe(null);
});
\ No newline at end of file
This diff is collapsed.
import {ProjectCreate} from './create';
import {ProjectEdit} from './edit';
export {ProjectCreate, ProjectEdit} ;
......@@ -6,6 +6,7 @@ import {
} from 'react-router-dom';
import {NotFound} from '../layout/components/NotFound';
import {ProjectCreate, ProjectEdit} from './Project';
import {Dashboard} from './Dashboard';
import {Scheduling} from './Scheduling';
import {TaskEdit, TaskView} from './Task';
......@@ -17,6 +18,10 @@ export const RoutedContent = () => {
<Redirect from="/" to="/" exact />
<Route path="/not-found" exact component= {NotFound} />
<Route path="/dashboard" exact component={Dashboard} />
<Route path="/project" exact component={NotFound} />
<Route path="/project/create" exact component={ProjectCreate} />
<Route path="/project/edit" exact component={ProjectEdit} />
<Route path="/project/edit/:id" exact component={ProjectEdit} />
<Route path="/scheduling" exact component={Scheduling} />
<Route path="/task" exact component={TaskView} />
<Route path="/task/view" exact component={TaskView} />
......
const axios = require('axios');
//axios.defaults.baseURL = 'http://192.168.99.100:8008/api';
axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0';
const CycleService = {
getAllCycles: async function() {
try {
const url = `/api/cycle`;
const response = await axios.get(url);
return response.data.results;
} catch (error) {
console.error(error);
}
},
}
export default CycleService;
\ No newline at end of file
import _ from 'lodash';
const axios = require('axios');
axios.defaults.headers.common['Authorization'] = 'Basic dGVzdDp0ZXN0';
const ProjectService = {
getProjectCategories: async function() {
try {
const url = `/api/cycle`;
const response = await axios.get(url);
//return response.data.results;
return [
{id: 1, name: "Regular"},
{id: 2, name: "User Shared Support"},
{id: 3, name: "Commissioning"},
{id: 4, name: "DDT"},
{id: 5, name: "Test"}
];
} catch (error) {
console.error(error);
}
},
getPeriodCategories: async function() {
try {
const url = `/api/cycle`;
const response = await axios.get(url);
// return response.data.results;
return [
{id: 1, name: "Single Cycle"},
{id: 2, name: "Long Term"},
{id: 3, name: "Unbounded"}
];
} catch (error) {
console.error(error);
}
},
getResources: async function() {
return this.getResourceTypes()
.then(resourceTypes => {
return this.getResourceUnits()
.then(resourceUnits => {
for (let resourceType of resourceTypes) {
resourceType.resourceUnit = _.find(resourceUnits, ['name', resourceType.resource_unit_id]);
}
return resourceTypes;
})
})
},
getResourceTypes: async function() {
try {
// const url = `/api/resource_type/?ordering=name`;
const url = `/api/resource_type`;
const response = await axios.get(url);
// console.log(response);
return response.data.results;
} catch (error) {
console.error(error);
}
},
getResourceUnits: async function() {
try {
const url = `/api/resource_unit`;
const response = await axios.get(url);
// console.log(response);
return response.data.results;
} catch (error) {
console.error(error);
}
},
getDefaultProjectResources: async function() {
try {
const url = `/api/resource_unit`;
const response = await axios.get(url);
// return response.data.results;
return {'LOFAR Observing Time': 3600,
'LOFAR Observing Time prio A': 3600,
'LOFAR Observing Time prio B': 3600,
'LOFAR Processing Time': 3600,
'Allocation storage': 1024*1024*1024*1024,
'Number of triggers': 1,
'LOFAR Support hours': 3600};
} catch (error) {
console.error(error);
}
},
saveProject: async function(project, projectQuota) {
try {
const response = await axios.post(('/api/project/'), project);
project = response.data
for (let quota of projectQuota) {
quota.project = project.url;
this.saveProjectQuota(quota);
}
return response.data;
} catch (error) {
// console.log(error);
console.log(error.response.data);
return error.response.data;
}
},
saveProjectQuota: async function(projectQuota) {
try {
const response = await axios.post(('/api/project_quota/'), projectQuota);
return response.data;
} catch (error) {
console.error(error);
return null;
}
},
getProjects: async function() {
try {
const response = await axios.get(('/api/project/'));
let projects = response.data.results;
const response1 = await axios.get(('/api/project_quota'));
const allProjectQuota = response1.data.results;
for (let project of projects) {
let projectQuota = _.filter(allProjectQuota, function(projQuota) { return _.includes(project.project_quota_ids, projQuota.id)});
for (const quota of projectQuota) {
project[quota.resource_type_id] = quota;
}
}
return response.data.results;
} catch (error) {
console.error(error);
return null;
}
},
getProjectQuota: async function(quotaId) {
try {
const response = await axios.get((`/api/project_quota/${quotaId}`));
return response.data;
} catch (error) {
console.error(error);
return null;
}
}
}
export default ProjectService;
\ No newline at end of file
const UnitConverter = {
resourceUnitMap: {'second':{display: 'Hours', conversionFactor: 3600, mode:'decimal', minFractionDigits:0, maxFractionDigits: 2 },
'byte': {display: 'TB', conversionFactor: (1024*1024*1024*1024), mode:'decimal', minFractionDigits:0, maxFractionDigits: 3},
'number': {display: 'Numbers', conversionFactor: 1, mode:'decimal', minFractionDigits:0, maxFractionDigits: 0}},
getDBResourceUnit: function() {
},
getUIResourceUnit: function() {
}
};
export default UnitConverter;
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment