Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/client/app/components/csv/MetersCSVUploadComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { selectSelectedLanguage } from '../../redux/slices/appStateSlice'; //import for internationlization
import * as React from 'react';
import { Button, Col, Container, Form, FormGroup, Input, Label, Row } from 'reactstrap';
import { MetersCSVUploadPreferences } from '../../types/csvUploadForm';
Expand Down Expand Up @@ -45,6 +45,7 @@ export default function MetersCSVUploadComponent() {
// to the attempted destination URL.
const navigate = useNavigate();

const language = useAppSelector(selectSelectedLanguage); //get user's selected language for i18n
const [meterData, setMeterData] = React.useState<MetersCSVUploadPreferences>(MetersCSVUploadDefaults);
const [selectedFile, setSelectedFile] = React.useState<File | null>(null);
const [isValidFileType, setIsValidFileType] = React.useState<boolean>(false);
Expand Down Expand Up @@ -106,7 +107,8 @@ export default function MetersCSVUploadComponent() {
if (selectedFile) {
// show spinner before calling api, then stop it immediately after
setShowSpinner(true);
const { success, message } = await submitMeters(meterData, selectedFile, dispatch);
//pass language for server side translations
const { success, message } = await submitMeters(meterData, selectedFile, dispatch, language);
setShowSpinner(false);
if (success) {
showSuccessNotification(message);
Expand Down
6 changes: 3 additions & 3 deletions src/client/app/components/csv/ReadingsCSVUploadComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { selectSelectedLanguage } from '../../redux/slices/appStateSlice';//import for internationalization
import { range } from 'lodash';
import * as React from 'react';
import { useEffect, useState } from 'react';
Expand Down Expand Up @@ -32,7 +32,6 @@ import { useBlocker, useNavigate } from 'react-router-dom';
*/
export default function ReadingsCSVUploadComponent() {
const translate = useTranslate();

// boolean that updates if any change is made to any readings modal
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
const [showUnsavedWarning, setShowUnsavedWarning] = useState(false);
Expand All @@ -50,6 +49,7 @@ export default function ReadingsCSVUploadComponent() {
// to the attempted destination URL.
const navigate = useNavigate();

const language = useAppSelector(selectSelectedLanguage);//get user's selected language i18n
const dispatch = useAppDispatch();
// Check for admin status
const isAdmin = useAppSelector(selectIsAdmin);
Expand Down Expand Up @@ -194,7 +194,7 @@ export default function ReadingsCSVUploadComponent() {
if (selectedFile) {
// show spinner before calling api, then stop it immediately after
setShowSpinner(true);
const { success, message } = await submitReadings(readingsData, selectedFile, dispatch);
const { success, message } = await submitReadings(readingsData, selectedFile, dispatch, language); // passed language
setShowSpinner(false);
if (success) {
showSuccessNotification(message);
Expand Down
20 changes: 9 additions & 11 deletions src/client/app/utils/api/UploadCSVApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ interface ApiResponse {
}

export const submitReadings = async (uploadPreferences: ReadingsCSVUploadPreferences, readingsFile: File,
dispatch: Dispatch): Promise<ApiResponse> => {
dispatch: Dispatch, language: string): Promise<ApiResponse> => { //added language parameter for i18n
const backend = new ApiBackend();
const formData = new FormData();
// The Boolean values in state must be converted to the submitted values of yes and no.
const uploadPreferencesForm: ReadingsCSVUploadPreferences = {
...uploadPreferences,
gzip: uploadPreferences.gzip,
Expand All @@ -37,11 +36,12 @@ export const submitReadings = async (uploadPreferences: ReadingsCSVUploadPrefere
for (const [preference, value] of Object.entries(uploadPreferencesForm)) {
formData.append(preference, value.toString());
}
formData.append('csvfile', readingsFile); // It is important for the server that the file is attached last.
formData.append('csvfile', readingsFile);

let message = '';
try {
message = await backend.doPostRequest<string>('/api/csv/readings', formData);
//sends langauge preference to server
message = await backend.doPostRequest<string>('/api/csv/readings', formData, {}, { 'Accept-Language': language });
dispatch(baseApi.util.invalidateTags(['Readings']));
return { success: true, message: message };
} catch (error) {
Expand All @@ -50,10 +50,9 @@ export const submitReadings = async (uploadPreferences: ReadingsCSVUploadPrefere
};

export const submitMeters = async (uploadPreferences: MetersCSVUploadPreferences, metersFile: File,
dispatch: Dispatch): Promise<ApiResponse> => {
dispatch: Dispatch, language: string): Promise<ApiResponse> => { //added language for i18n
const backend = new ApiBackend();
const formData = new FormData();
// The Boolean values in state must be converted to the submitted values of yes and no.
const uploadPreferencesForm: CSVUploadPreferences = {
...uploadPreferences,
gzip: uploadPreferences.gzip,
Expand All @@ -63,15 +62,14 @@ export const submitMeters = async (uploadPreferences: MetersCSVUploadPreferences
for (const [preference, value] of Object.entries(uploadPreferencesForm)) {
formData.append(preference, value.toString());
}
formData.append('csvfile', metersFile); // It is important for the server that the file is attached last.
formData.append('csvfile', metersFile);

try {
const response = await backend.doPostRequest<string>('/api/csv/meters', formData);
// Meter Data was sent to the DB, invalidate meters for now
//send langauge preference to server
const response = await backend.doPostRequest<string>('/api/csv/meters', formData, {}, { 'Accept-Language': language });
dispatch(baseApi.util.invalidateTags(['MeterData']));
// meters were invalidated so all meter changes will now reflect in Redux state, now return
return { success: true, message: response };
} catch (error) {
return { success: false, message: error.response.data };
}
};
};
45 changes: 33 additions & 12 deletions src/server/routes/csv.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* meter and readings data.
*/


const { translate, getLanguageFromRequest } = require('../translations/translate');

const moment = require('moment');
const crypto = require('crypto');
const express = require('express');
Expand Down Expand Up @@ -101,13 +104,19 @@ router.use(function (req, res, next) {
// We need this extra middleware because multer does not provide an option to guard against the case where no file is uploaded.
router.use(function (req, res, next) {
if (!req.file) {
failure(req, res, new CSVPipelineError('No csv file was uploaded. A csv file must be submitted via the csvfile parameter.'));
//failure(req, res, new CSVPipelineError('No csv file was uploaded. A csv file must be submitted via the csvfile parameter.'));
const language = getLanguageFromRequest(req);
const errorMessage = translate('csv.upload.error.no.file', language);
failure(req, res, new CSVPipelineError(errorMessage));

} else {
next();
}
});

router.post('/meters', validateMetersCsvUploadParams, async (req, res) => {
const language = getLanguageFromRequest(req);
const t = (key) => translate(key, language);
const isGzip = normalizeBoolean(req.body.gzip);
const uploadedFilepath = req.file.path;
let csvFilepath;
Expand All @@ -120,15 +129,16 @@ router.post('/meters', validateMetersCsvUploadParams, async (req, res) => {
fileBuffer = zlib.gunzipSync(fileBuffer);
// We expect this directory to have been created by this stage of the pipeline.
const dir = `${__dirname}/../tmp/uploads/csvPipeline`;
csvFilepath = await saveCsv(fileBuffer, 'meters', dir);
csvFilepath = await saveCsv(fileBuffer, 'meters', dir,language); // pass lagnauge for translated error messages
log.info(`The unzipped file ${csvFilepath} was created to upload meters csv data`);
} else {
csvFilepath = uploadedFilepath;
}

const conn = getConnection();
await uploadMeters(req, res, csvFilepath, conn);
success(req, res, 'Successfully inserted the meters.');
await uploadMeters(req, res, csvFilepath, conn, language);
//success(req, res, 'Successfully inserted the meters.');
success(req, res, t('csv.upload.meters.success'));
} catch (error) {
failure(req, res, error);

Expand All @@ -150,9 +160,15 @@ router.post('/meters', validateMetersCsvUploadParams, async (req, res) => {
});
}
}
});
});

router.post('/readings', validateReadingsCsvUploadParams, async (req, res) => {


const language = getLanguageFromRequest(req);
const t = (key) => translate(key, language);


const isGzip = normalizeBoolean(req.body.gzip);
const isRefreshReadings = normalizeBoolean(req.body.refreshReadings);
const uploadedFilepath = req.file.path;
Expand All @@ -168,13 +184,13 @@ router.post('/readings', validateReadingsCsvUploadParams, async (req, res) => {
fileBuffer = zlib.gunzipSync(fileBuffer);
// We expect this directory to have been created by this stage of the pipeline.
const dir = `${__dirname}/../tmp/uploads/csvPipeline`;
csvFilepath = await saveCsv(fileBuffer, 'readings', dir);
csvFilepath = await saveCsv(fileBuffer, 'readings', dir, language);
log.info(`The unzipped file ${csvFilepath} was created to upload readings csv data`);
} else {
csvFilepath = uploadedFilepath;
}
const conn = getConnection();
({ isAllReadingsOk, msgTotal } = await uploadReadings(req, res, csvFilepath, conn));
({ isAllReadingsOk, msgTotal } = await uploadReadings(req, res, csvFilepath, conn, language));
if (isRefreshReadings) {
// Refresh readings so show when daily data is used.
await refreshAllReadingViews();
Expand Down Expand Up @@ -202,14 +218,19 @@ router.post('/readings', validateReadingsCsvUploadParams, async (req, res) => {
}
let message;
if (isAllReadingsOk) {
message = '<h2>It looks like the insert of the readings was a success.</h2>'
//message = '<h2>It looks like the insert of the readings was a success.</h2>'
//if (msgTotal !== '') {
// message += '<h3>However, note that the processing of the readings returned these warning(s):</h3>' + msgTotal;
message = `<h2>${t('csv.upload.readings.success.complete')}</h2>`;
if (msgTotal !== '') {
message += '<h3>However, note that the processing of the readings returned these warning(s):</h3>' + msgTotal;
}
message += `<h3>${t('csv.upload.readings.success.with.warnings')}</h3>` + msgTotal;
}
success(req, res, message);
} else {
message = '<h2>It looks like the insert of the readings had issues with some or all of the readings where' +
' the processing of the readings returned these warning(s)/error(s):</h2>' + msgTotal;
//message = '<h2>It looks like the insert of the readings had issues with some or all of the readings where' +
//' the processing of the readings returned these warning(s)/error(s):</h2>' + msgTotal;
message = `<h2>${t('csv.upload.readings.error.has.issues')}</h2>` + msgTotal;

failure(req, res, message);
}
});
Expand Down
5 changes: 3 additions & 2 deletions src/server/services/pipeline-in-progress/loadCsvInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ async function loadCsvInput(
honorDst = false,
relaxedParsing = false,
useMeterZone = false,
warnOnCumulativeReset = false
warnOnCumulativeReset = false,
language = 'en' //added for internationalization: language preference from user
) {
try {
const dataRows = await readCsv(filePath, headerRow);
return loadArrayInput(dataRows, meterID, mapRowToModel, timeSort, readingRepetition,
isCumulative, cumulativeReset, cumulativeResetStart, cumulativeResetEnd,
readingGap, readingLengthVariation, isEndOnly, shouldUpdate, conditionSet, conn,
honorDst, relaxedParsing, useMeterZone, warnOnCumulativeReset);
honorDst, relaxedParsing, useMeterZone, warnOnCumulativeReset, language);
} catch (err) {
log.error(`Error updating meter ${meterID} with data from ${filePath}: ${err}`, err);
}
Expand Down
Loading
Loading