diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5c42db8e6a19f5300242eabab4fb0fdaa10eca3c..90afa279bd26d08bb82fbf6f4fc941a149151802 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -258,6 +258,8 @@ Prepare_Packages_TMSS_FrontEnd: Build_TMSS_FrontEnd: stage: build image: node:20 + variables : + GIT_CLEAN_FLAGS: -ffdx -e SAS/TMSS/frontend/tmss_webapp/ dependencies: - Prepare_Packages_TMSS_FrontEnd script: @@ -285,6 +287,8 @@ test_TMSS_Frontend: stage: test image: node:20 allow_failure: true + variables : + GIT_CLEAN_FLAGS: -ffdx -e SAS/TMSS/frontend/tmss_webapp/ script: - cd SAS/TMSS/frontend/tmss_webapp - npm ci --cache .npm --prefer-offline @@ -338,18 +342,19 @@ deploy_TMSSFrontEnd_Acceptance: environment: Acceptance image: node:20 when: manual - variables : - GIT_CLEAN_FLAGS: -ffdx -e SAS/TMSS/frontend/tmss_webapp/build/ + allow_failure: true script: - - echo "Deployiing TMSS FrontEnd..." + - echo "Deployiing TMSS FrontEnd to Acceptance..." - cd SAS/TMSS/frontend/tmss_webapp - ls -al - npm ci --cache .npm --prefer-offline + - export DISABLE_ESLINT_PLUGIN=true + - export PUBLIC_URL=https://tmss.lofar.eu/acc/ + - npm run build - npm run deployacc - needs: - - Build_TMSS_FrontEnd + build_LTAIngest: stage: build diff --git a/SAS/TMSS/frontend/tmss_webapp/deployacc.js b/SAS/TMSS/frontend/tmss_webapp/deployacc.js index df57c60aa5e6bfe477e918aa381c75afaef0131e..2be57bb33adb12c1300c614daf106784f715d4d5 100644 --- a/SAS/TMSS/frontend/tmss_webapp/deployacc.js +++ b/SAS/TMSS/frontend/tmss_webapp/deployacc.js @@ -1,63 +1,76 @@ require('dotenv').config(); const fs = require('fs'); const path = require('path'); - +var mime = require('mime-types') const Minio = require('minio'); const minioClient = new Minio.Client({ endPoint: 'monitor.control.lofar', - port: 9000, - useSSL: false, + port: 9000, + useSSL: false, accessKey: process.env.MINIO_ACCESS_KEY, secretKey: process.env.MINIO_SECRET, }); const bucketName = "tmss-frontend-web-acceptance"; -const sourceFolder = 'build'; +const sourceFolder = 'build'; async function uploadRecursive(folderPath) { - const items = fs.readdirSync(folderPath); - - for (const item of items) { - const itemPath = path.join(folderPath, item); - - if (fs.statSync(itemPath).isDirectory()) { - // If it's a directory, recursively upload its contents - await uploadRecursive(itemPath); - } else { - // If it's a file, upload it to Minio - const minioObjectName = path.relative(sourceFolder, itemPath).replace(/\\/g, '/'); - const metaData = { - 'Content-Type': 'application/octet-stream', - }; - - await minioClient.fPutObject(bucketName, minioObjectName, itemPath, metaData); - console.log(`File "${item}" uploaded successfully.`); - } + const items = fs.readdirSync(folderPath); + + for (const item of items) { + const itemPath = path.join(folderPath, item); + + if (fs.statSync(itemPath).isDirectory()) { + await uploadRecursive(itemPath); // If it's a directory, recursively upload its contents + } else { + // If it's a file, upload it to Minio + uploadFile(itemPath, item); + } + } +} + +async function uploadFile(itemPath, item) { + const minioObjectName = path.relative("", itemPath).replace(/\\/g, '/'); + let mimetype = mime.lookup(minioObjectName); + try { + + let metaData = { 'Content-Type': mimetype, }; + + if (item.endsWith("html") ) { metaData = { 'Content-Type': 'text/html', };} + + + + let putResult = await minioClient.fPutObject(bucketName, minioObjectName, itemPath, metaData); + if (putResult.err) { + console.log(`File "${item}" Not uploaded . {putResult?.err} ${mimetype}`); + } + else { + console.log(`File "${item}" uploaded successfully to ${itemPath} etag: ${putResult?.etag} ${mimetype}`); } } + catch (e) { + console.log(`File "${itemPath} ${item}" Not uploaded to ${minioObjectName} ${mimetype} ${e}`); + } +} async function uploadFolderToMinio() { - try { - // Create bucket if it doesn't exist - const bucketExists = await minioClient.bucketExists(bucketName); - if (!bucketExists) { - // await minioClient.makeBucket(bucketName); - // console.log(`Bucket "${bucketName}" created successfully.`); - console.error(" bucket did not exist, exitting") - return - } - - // Function to recursively upload items in a folder - - - // Start recursive upload - await uploadRecursive(sourceFolder); - - console.log('Folder uploaded successfully.'); - } catch (err) { - console.error('An error occurred:', err); + try { + // BUcket should exist + /* + const bucketExists = await minioClient.bucketExists(bucketName); + if (!bucketExists) { + console.error(" bucket did not exist, exitting") + return } + */ + + await uploadRecursive(sourceFolder); // Start recursive upload + + console.log('Folder uploaded successfully.'); + } catch (err) { + console.error('An error occurred:', err); } - - uploadFolderToMinio() \ No newline at end of file +} + +uploadFolderToMinio() \ No newline at end of file diff --git a/SAS/TMSS/frontend/tmss_webapp/package-lock.json b/SAS/TMSS/frontend/tmss_webapp/package-lock.json index 8eb43732263813e374c36dc51ff4558fda010660..b12615585f08ddb43d617d875a293f959e7e44d1 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package-lock.json +++ b/SAS/TMSS/frontend/tmss_webapp/package-lock.json @@ -114,6 +114,7 @@ "jest-websocket-mock": "^2.5.0", "@testing-library/jest-dom": "^6.4.1", "@types/react-router-dom": "^5.3.3", + "mime-types": "^2.1.35", "jest-canvas-mock": "^2.5.2", "react-app-rewired": "^2.2.1", "customize-cra": "^1.0.0", @@ -5138,7 +5139,8 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { "mime-db": "1.52.0" }, diff --git a/SAS/TMSS/frontend/tmss_webapp/package.json b/SAS/TMSS/frontend/tmss_webapp/package.json index 773b5d3451899b3bbc228748360c533286b6970d..f56baf4b6741a365d766cc0d6221765b244712cc 100644 --- a/SAS/TMSS/frontend/tmss_webapp/package.json +++ b/SAS/TMSS/frontend/tmss_webapp/package.json @@ -117,10 +117,10 @@ "prepareTemplateSchemasDev": "node prepareTemplateSchemas_dev.js", "prepareTemplateSchemas": "node prepareTemplateSchemas.js", "cleanTemplateSchemas": "node cleanTemplateSchemas.js", - "deployacc":"node deployacc.js", + "deployacc": "node deployacc.js", "test:ci": "react-scripts test -all --collectCoverage --coverageDirectory=\"./coverage\" --ci --reporters=default --reporters=jest-junit --watchAll=false" }, - "proxy": "http://127.0.0.1:8008", + "proxy": "http://localhost:8008", "eslintConfig": { "extends": "react-app" }, @@ -165,6 +165,7 @@ "jest-mock-console": "^2.0.0", "jest-websocket-mock": "^2.5.0", "js-beautify": "^1.14.11", + "mime-types": "^2.1.35", "minio": "^7.1.3", "npm-check-updates": "^16.14.12", "react-app-rewired": "^2.2.1", diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js index 01a275a21f17ce12b863cb0036c5a323dc28ddbe..ddd909c73718ab568a3f43cf311a1d2e81857ae7 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.create.js @@ -352,10 +352,12 @@ export class ReservationCreate extends Component { reservation['start_time'] = moment(reservation['start_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT); reservation['stop_time'] = reservation['stop_time'] ? moment(reservation['stop_time']).format(UIConstants.CALENDAR_DATETIME_FORMAT) : null; reservation['projects'] = selectedProjectURLs + if (reservation['projects'].length==0) reservation['projects']=null reservation['specifications_template'] = this.state.reservationTemplate.url; reservation['specifications_doc'] = this.paramsOutput; - reservation = await ReservationService.saveReservation(reservation); - if (reservation?.id) { + let result = await ReservationService.saveReservation(reservation); + + if (result?.id) { appGrowl.show({severity: 'success', summary: 'Success', detail: 'Reservation is created successfully.'}); if (this.state.createAnother) { this.setState({paramsOutput: {}, showDialog: false, isDirty: false }); @@ -366,6 +368,21 @@ export class ReservationCreate extends Component { publish('edit-dirty', false); } + else + { + let errormessage="Unable to Save" + try { + errormessage = Object.keys(result.response.data)[0] + " -" + Object.values(result.response.data)[0] + errormessage = errormessage.replace("null","empty") + }catch(e){ + errormessage="Unknown Error. could not Save" + } + + if (result?.response?.status==400) { + appGrowl.show({severity: 'error', summary: 'Failed to save', detail: errormessage}); + } + } + } /** diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js index ed1933362d3e894fc86a852ed99e539f757dd18c..bc46eebf885143d50f708d606a526bf20747034f 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.edit.js @@ -509,7 +509,7 @@ export class ReservationEdit extends Component { </div> <div className="p-field p-grid"> - <label htmlFor="project" className="col-lg-2 col-md-2 col-sm-12">Project</label> + <label htmlFor="projects" className="col-lg-2 col-md-2 col-sm-12">Project</label> <div className="col-lg-3 col-md-3 col-sm-12" data-testid="project" > <MultiSelect inputId="project" optionLabel="name" optionValue="name" tooltip="Projects" tooltipOptions={this.tooltipOptions} diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.summary.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.summary.js index 8b066ddd006c34e4e379bf5ee5386eb8ff134574..5a53de9de863460cd99484b7f7bff18ee30deeec 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.summary.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Reservation/reservation.summary.js @@ -114,7 +114,7 @@ export class ReservationSummary extends Component { let summaryFields = [ { labelName: "Name", value: reservation.name, formatTime: false }, { labelName: "Description", value: reservation.description, formatTime: false }, - { labelName: "Project", value: reservation.project_id, formatTime: false }, + { labelName: "Project", value: reservation.projects_ids?.join(", "), formatTime: false }, { labelName: "Start Time", value: reservation.start_time, formatTime: true }, { labelName: "Stop Time", value: hasStopTime ? reservation.stop_time : "-", formatTime: hasStopTime }, { labelName: "Duration (HH:mm:ss)", value: reservation.stop_time ? UnitConverter.getSecsToHHmmss(reservation.duration) : "Unknown", formatTime: false }, @@ -135,7 +135,7 @@ export class ReservationSummary extends Component { {reservation.specifications_doc && <div className="col-12 constraints-summary"> <label>Parameters:</label> - <JsonToTable json={this.getOrderedSpecifications(specifications)} /> + <JsonToTable json={this.getOrderedSpecifications(specifications)} style={{Height:"800px"}}/> </div> } </div> diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/toolbar/Filters.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/toolbar/Filters.js index 864c954a92ddb3e815381f3f1535fdeab58c4770..0ebcd694a7a348d85aea14310589c25c199e3165 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/toolbar/Filters.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/components/toolbar/Filters.js @@ -123,7 +123,7 @@ export default function Filters(props) { const filteredProjectsOnState = multiSelectAllOptions?.projectNamesWithState?.filter(project => projectStatusFilter.includes(project?.project_state_value)) setProjectAllOptionsWithStatus(filteredProjectsOnState) - + if (filteredProjectsOnState?.length > 0) { setProjectFilter(filteredProjectsOnState.map(project => project.name)) } diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/toolbar/filters.helper.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/toolbar/filters.helper.js index 3e736b284707ed61221b334170662f4f69485745..f65d44285b1b1a86d43fde86f2b1fdf52e4de780 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/toolbar/filters.helper.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Timeline/helpers/toolbar/filters.helper.js @@ -14,31 +14,31 @@ function determineNewStartAndEnd(startTime, day, spanInDays, endTime) { const formattedStart = start.format(UIConstants.UTC_DATE_TIME_FORMAT); const formattedEnd = end.format(UIConstants.UTC_DATE_TIME_FORMAT); - return { formattedStart, formattedEnd }; + return { formattedStart, formattedEnd, start, end }; } -function splitElementPerDay(element, spanInDays, shouldShowOnSkyTimes) { +function splitElementPerDay(element, spanInDays, shouldShowOnSkyTimes, viewStartTime, viewEndTime) { const newElements = [] for (let day = 0; day <= spanInDays; day++) { let newElement = { ...element } - - const startTime = (shouldShowOnSkyTimes ? (element.on_sky_start_time ?? element.process_start_time) : element.process_start_time) ?? element.start_time - const endTime = (shouldShowOnSkyTimes ? (element.on_sky_stop_time ?? element.process_stop_time) : element.process_stop_time) ?? element.stop_time - const { formattedStart, formattedEnd } = determineNewStartAndEnd(startTime, day, spanInDays, endTime); - if (shouldShowOnSkyTimes) { - newElement.on_sky_start_time = formattedStart - newElement.on_sky_stop_time = formattedEnd - } else if (element.process_start_time) { //it is a SU unit with process start/stop times + const startTime = (shouldShowOnSkyTimes ? (element.on_sky_start_time ?? element.process_start_time) : element.process_start_time) ?? element.start_time + const endTime = (shouldShowOnSkyTimes ? (element.on_sky_stop_time ?? element.process_stop_time) : element.process_stop_time) ?? element.stop_time + const { formattedStart, formattedEnd, start, end } = determineNewStartAndEnd(startTime, day, spanInDays, endTime); + + if (start.isSameOrAfter(viewStartTime) && end.isSameOrBefore(viewEndTime)) { + if (shouldShowOnSkyTimes) { + newElement.on_sky_start_time = formattedStart + newElement.on_sky_stop_time = formattedEnd + } else if (element.process_start_time) { //it is a SU unit with process start/stop times newElement.process_start_time = formattedStart newElement.process_stop_time = formattedEnd } else { //it is a reservation with only start/stop times newElement.start_time = formattedStart newElement.stop_time = formattedEnd } - - - newElements.push(newElement) + newElements.push(newElement) + } } return newElements } @@ -49,18 +49,25 @@ function splitElementPerDay(element, spanInDays, shouldShowOnSkyTimes) { * @param shouldShowOnSkyTimes determines whether to use the process times or on sky times * @return [elements] (nested thus use flatMap instead of map) */ -export function splitObjectIfSpanIsMultipleDays(element, shouldShowOnSkyTimes) { - const startTime = (shouldShowOnSkyTimes ? (element.on_sky_start_time ?? element.process_start_time) : element.process_start_time) ?? element.start_time +export function splitObjectIfSpanIsMultipleDays(element, shouldShowOnSkyTimes, viewStartTime, viewEndTime) { + let startTime = (shouldShowOnSkyTimes ? (element.on_sky_start_time ?? element.process_start_time) : element.process_start_time) ?? element.start_time + let stopTime = (shouldShowOnSkyTimes ? (element.on_sky_stop_time ?? element.process_stop_time) : element.process_stop_time) ?? element.stop_time + + + if (!startTime) { throw new ReferenceError("(On Sky) start time is not defined for:\n" + JSON.stringify(element)) } + + element.real_start_time = moment(startTime).format(UIConstants.CALENDAR_DATETIME_FORMAT) - const stopTime = (shouldShowOnSkyTimes ? (element.on_sky_stop_time ?? element.process_stop_time) : element.process_stop_time) ?? element.stop_time + element.real_end_time = "Unknown" let spanInDays = 7 //initial amount of days if stop time is unknown - if (stopTime) { + if (stopTime) { + element.real_end_time = moment(stopTime).format(UIConstants.CALENDAR_DATETIME_FORMAT) spanInDays = moment(stopTime).startOf('day').diff(moment(startTime).startOf('day'), 'days') } @@ -69,7 +76,7 @@ export function splitObjectIfSpanIsMultipleDays(element, shouldShowOnSkyTimes) { } if (spanInDays) { - return splitElementPerDay(element, spanInDays, shouldShowOnSkyTimes) + return splitElementPerDay(element, spanInDays, shouldShowOnSkyTimes, viewStartTime, viewEndTime) } return [element] } @@ -138,11 +145,11 @@ export function getTimelineItem(suBlueprint, displayDate, includeStationsInfo, i const stations = getStationsInfo(suBlueprint.specified_stations); const timeLineRow = { - + suId: suBlueprint.id, project: suBlueprint.project, project_group: suBlueprint.project + '#' + currentGrouping, - project_task_group : suBlueprint.project + '#' + currentGrouping + "#tasks", + project_task_group: suBlueprint.project + '#' + currentGrouping + "#tasks", name: suBlueprint.name, name_group: suBlueprint.name + '#' + currentGrouping, antenna_set: suBlueprint.antenna_set, @@ -166,12 +173,12 @@ export function getTimelineItem(suBlueprint, displayDate, includeStationsInfo, i //for item pop-over real_start_time: suBlueprint.real_start_time, real_end_time: suBlueprint.real_end_time, - SkyTimesOnTimes:shouldShowOnSkyTimes, - displayDate:displayDate + SkyTimesOnTimes: shouldShowOnSkyTimes, + displayDate: displayDate - } - if (suBlueprint.task_blueprints) { - timeLineRow.task_blueprints =suBlueprint.task_blueprints + } + if (suBlueprint.task_blueprints) { + timeLineRow.task_blueprints = suBlueprint.task_blueprints } setGroupingKeys(timeLineRow, currentGrouping, includeStationsInfo, suBlueprint, includeStationsgroupInfo, stations); @@ -223,8 +230,10 @@ export function getReservationItem(reservation, index, displayDate) { const currentGroup = moment(reservation.start_time).format(UIConstants.CALENDAR_GROUP_FORMAT) const currentGrouping = moment(reservation.start_time).format(UIConstants.CALENDAR_DEFAULTDATE_FORMAT) - let project = reservation.projects_ids?.join(", ") - + let project = reservation.projects_ids?.join(", ") + + if (!project) project = null + let timeLineRow = { id: reservation.id + "~" + index, reservationId: reservation.id, @@ -233,9 +242,9 @@ export function getReservationItem(reservation, index, displayDate) { group: currentGroup, name: reservation.name, project: project, - reservation_start_time : moment(reservation.start_time), + reservation_start_time: moment(reservation.start_time), type: 'RESERVATION', - activity_type: `${type}${reservation.project_id ? (" - " + reservation.project_id) : ""}`, + activity_type: `${type}${project ? (" - " + project) : ""}`, reason: type, title: '', stations: stations.summary, @@ -249,16 +258,16 @@ export function getReservationItem(reservation, index, displayDate) { selectedBgColor: blockColor.bgColor, color: blockColor.color }; - + timeLineRow.specified_stations = specificationsDoc?.resources?.stations; timeLineRow.used_stations = specificationsDoc?.resources?.stations; - timeLineRow.missing_stations = specificationsDoc?.resources?.stations; - - + timeLineRow.missing_stations = specificationsDoc?.resources?.stations; + + timeLineRow.name_group = timeLineRow.name + '#' + currentGrouping timeLineRow.project_group = timeLineRow.project + '#' + currentGrouping - timeLineRow.project_task_group = timeLineRow.project + '#' + currentGrouping ; // we do not have tasks, but we need to be visible. + timeLineRow.project_task_group = timeLineRow.project + '#' + currentGrouping; // we do not have tasks, but we need to be visible. timeLineRow.stationsgroups = stations.stationsgroups; timeLineRow.groupingSuffix = '#' + currentGrouping; return timeLineRow; diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js index 8b6b13e9bf79d0b9d3e5b65434dd6be72af4811e..82bafe57a23b839c12b489f491f0feb16a4e3f20 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/index.js @@ -171,6 +171,12 @@ export const routes = [ title: 'Cycle-List', permissions: ['cycle', 'list'] }, + { + path: "/acc", + component: WeekView, + name: 'Scheduling Unit Timeline - Week', + title: 'SU Weekly Timeline View' + }, { path: "/su/timelineview/week", component: WeekView, diff --git a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js index 3a63776d80d703d4fc7d08b73b0edf769d7bb63b..ae10e6b112f440eca134d0bea3128544792306d0 100644 --- a/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js +++ b/SAS/TMSS/frontend/tmss_webapp/src/services/reservation.service.js @@ -15,8 +15,8 @@ const ReservationService = { const response = await axios.post(('/api/reservation/'), reservation); return response.data; } catch (error) { - console.error(error); - return null; + console.error("Error Saving", error); + return error; } }, updateReservation: async function (reservation) {